@mastra/factory 0.26.0-alpha.0 → 0.26.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 (224) hide show
  1. package/README.md +158 -57
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/boards/card-facts.d.ts +5 -0
  4. package/dist/boards/card-facts.d.ts.map +1 -0
  5. package/dist/boards/card-facts.js +19 -0
  6. package/dist/boards/card-facts.js.map +1 -0
  7. package/dist/boards/define-board.d.ts +109 -13
  8. package/dist/boards/define-board.d.ts.map +1 -1
  9. package/dist/boards/define-board.js +163 -21
  10. package/dist/boards/define-board.js.map +1 -1
  11. package/dist/boards/index.d.ts +4 -3
  12. package/dist/boards/index.d.ts.map +1 -1
  13. package/dist/boards/index.js +3 -2
  14. package/dist/boards/index.js.map +1 -1
  15. package/dist/boards/legacy-plan-role.d.ts +14 -0
  16. package/dist/boards/legacy-plan-role.d.ts.map +1 -0
  17. package/dist/boards/legacy-plan-role.js +32 -0
  18. package/dist/boards/legacy-plan-role.js.map +1 -0
  19. package/dist/boards/phase-automation.d.ts +13 -0
  20. package/dist/boards/phase-automation.d.ts.map +1 -0
  21. package/dist/boards/phase-automation.js +25 -0
  22. package/dist/boards/phase-automation.js.map +1 -0
  23. package/dist/boards/phase-runner.d.ts +13 -0
  24. package/dist/boards/phase-runner.d.ts.map +1 -0
  25. package/dist/boards/phase-runner.js +44 -0
  26. package/dist/boards/phase-runner.js.map +1 -0
  27. package/dist/boards/relocate.d.ts.map +1 -1
  28. package/dist/boards/relocate.js +2 -0
  29. package/dist/boards/relocate.js.map +1 -1
  30. package/dist/boards/review.d.ts +1 -1
  31. package/dist/boards/review.d.ts.map +1 -1
  32. package/dist/boards/review.js +18 -18
  33. package/dist/boards/review.js.map +1 -1
  34. package/dist/boards/semantics.d.ts.map +1 -1
  35. package/dist/boards/semantics.js +11 -7
  36. package/dist/boards/semantics.js.map +1 -1
  37. package/dist/boards/transition-policy.d.ts +10 -10
  38. package/dist/boards/work-tool-rules.d.ts +3 -3
  39. package/dist/boards/work-tool-rules.d.ts.map +1 -1
  40. package/dist/boards/work-tool-rules.js +1 -1
  41. package/dist/boards/work-tool-rules.js.map +1 -1
  42. package/dist/boards/work.d.ts +3 -3
  43. package/dist/boards/work.d.ts.map +1 -1
  44. package/dist/boards/work.js +31 -29
  45. package/dist/boards/work.js.map +1 -1
  46. package/dist/environment/build-runner.d.ts +4 -4
  47. package/dist/environment/build-runner.d.ts.map +1 -1
  48. package/dist/environment/build-workflow.d.ts +6 -6
  49. package/dist/environment/build-workflow.js +1 -1
  50. package/dist/environment/build.d.ts +1 -1
  51. package/dist/environment/build.d.ts.map +1 -1
  52. package/dist/factory.d.ts +8 -0
  53. package/dist/factory.d.ts.map +1 -1
  54. package/dist/factory.js +6 -0
  55. package/dist/factory.js.map +1 -1
  56. package/dist/index.d.ts +3 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -2
  59. package/dist/integrations/github/app-identity.d.ts.map +1 -1
  60. package/dist/integrations/github/default-rules.d.ts +71 -71
  61. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  62. package/dist/integrations/github/integration.d.ts.map +1 -1
  63. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  64. package/dist/integrations/github/rules.d.ts.map +1 -1
  65. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  66. package/dist/integrations/gitlab/api.d.ts.map +1 -1
  67. package/dist/integrations/gitlab/default-rules.d.ts +38 -38
  68. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  69. package/dist/integrations/gitlab/integration.d.ts.map +1 -1
  70. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  71. package/dist/integrations/incidentio/api.d.ts.map +1 -1
  72. package/dist/integrations/incidentio/default-rules.d.ts +3 -3
  73. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -1
  74. package/dist/integrations/incidentio/integration.d.ts.map +1 -1
  75. package/dist/integrations/incidentio/rules.d.ts.map +1 -1
  76. package/dist/integrations/issue-reconcile-worker.d.ts.map +1 -1
  77. package/dist/integrations/jira/api.d.ts +1 -1
  78. package/dist/integrations/jira/api.d.ts.map +1 -1
  79. package/dist/integrations/jira/default-rules.d.ts +3 -3
  80. package/dist/integrations/jira/default-rules.d.ts.map +1 -1
  81. package/dist/integrations/jira/integration.d.ts.map +1 -1
  82. package/dist/integrations/jira/rules.d.ts.map +1 -1
  83. package/dist/integrations/linear/default-rules.d.ts +8 -8
  84. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  85. package/dist/integrations/linear/integration.d.ts.map +1 -1
  86. package/dist/integrations/linear/rules.d.ts.map +1 -1
  87. package/dist/integrations/platform/api-client.d.ts.map +1 -1
  88. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  89. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  90. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -1
  91. package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -1
  92. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  93. package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
  94. package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
  95. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  96. package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -1
  97. package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -1
  98. package/dist/integrations/slack/feed-publisher.d.ts.map +1 -1
  99. package/dist/integrations/slack/integration.d.ts.map +1 -1
  100. package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -1
  101. package/dist/integrations/slack/reply-tool.d.ts.map +1 -1
  102. package/dist/integrations/slack/slack.d.ts.map +1 -1
  103. package/dist/integrations/workos/integration.d.ts.map +1 -1
  104. package/dist/mcp/events.d.ts +3 -3
  105. package/dist/mcp/routes.js +1 -1
  106. package/dist/routes/attention-activity.d.ts +1 -1
  107. package/dist/routes/attention-activity.d.ts.map +1 -1
  108. package/dist/routes/attention-parked.d.ts +1 -1
  109. package/dist/routes/attention-parked.d.ts.map +1 -1
  110. package/dist/routes/attention-providers.d.ts +1 -1
  111. package/dist/routes/attention-providers.d.ts.map +1 -1
  112. package/dist/routes/automation-runs.d.ts.map +1 -1
  113. package/dist/routes/config.d.ts.map +1 -1
  114. package/dist/routes/contracts.d.ts +159 -136
  115. package/dist/routes/contracts.d.ts.map +1 -1
  116. package/dist/routes/contracts.js +23 -0
  117. package/dist/routes/contracts.js.map +1 -1
  118. package/dist/routes/fs.d.ts.map +1 -1
  119. package/dist/routes/fs.js +24 -0
  120. package/dist/routes/fs.js.map +1 -1
  121. package/dist/routes/intake.d.ts.map +1 -1
  122. package/dist/routes/knowledge.d.ts.map +1 -1
  123. package/dist/routes/oauth.d.ts.map +1 -1
  124. package/dist/routes/projects.d.ts.map +1 -1
  125. package/dist/routes/route.d.ts.map +1 -1
  126. package/dist/routes/skills.d.ts +3 -0
  127. package/dist/routes/skills.d.ts.map +1 -1
  128. package/dist/routes/skills.js +1 -1
  129. package/dist/routes/skills.js.map +1 -1
  130. package/dist/routes/surface.d.ts +3 -0
  131. package/dist/routes/surface.d.ts.map +1 -1
  132. package/dist/routes/surface.js +23 -22
  133. package/dist/routes/surface.js.map +1 -1
  134. package/dist/routes/telemetry.d.ts.map +1 -1
  135. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  136. package/dist/routes/work-items.d.ts.map +1 -1
  137. package/dist/routes/work-items.js +19 -3
  138. package/dist/routes/work-items.js.map +1 -1
  139. package/dist/rules/decision-applicability.d.ts.map +1 -1
  140. package/dist/rules/decision-applicability.js +3 -1
  141. package/dist/rules/decision-applicability.js.map +1 -1
  142. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  143. package/dist/rules/dispatcher.d.ts.map +1 -1
  144. package/dist/rules/dispatcher.js +17 -3
  145. package/dist/rules/dispatcher.js.map +1 -1
  146. package/dist/rules/processor.d.ts.map +1 -1
  147. package/dist/rules/processor.js +4 -2
  148. package/dist/rules/processor.js.map +1 -1
  149. package/dist/rules/rule-group.d.ts +29 -0
  150. package/dist/rules/rule-group.d.ts.map +1 -0
  151. package/dist/rules/rule-group.js +141 -0
  152. package/dist/rules/rule-group.js.map +1 -0
  153. package/dist/rules/start-coordinator.d.ts.map +1 -1
  154. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  155. package/dist/rules/tools.d.ts +1 -1
  156. package/dist/rules/tools.d.ts.map +1 -1
  157. package/dist/rules/transition-service.d.ts.map +1 -1
  158. package/dist/rules/transition-service.js +5 -3
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/rules/types.d.ts +25 -14
  161. package/dist/rules/types.d.ts.map +1 -1
  162. package/dist/rules/types.js.map +1 -1
  163. package/dist/rules/validation.d.ts +2 -0
  164. package/dist/rules/validation.d.ts.map +1 -1
  165. package/dist/rules/validation.js +6 -3
  166. package/dist/rules/validation.js.map +1 -1
  167. package/dist/sandbox/callback-factory-sandbox.d.ts.map +1 -1
  168. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
  169. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  170. package/dist/session/environment-state-processor.d.ts.map +1 -1
  171. package/dist/session/factory-session.d.ts.map +1 -1
  172. package/dist/session/live-sessions.d.ts.map +1 -1
  173. package/dist/session/reply-credential-processor.d.ts.map +1 -1
  174. package/dist/session/run-audit.d.ts +1 -1
  175. package/dist/session/run-audit.d.ts.map +1 -1
  176. package/dist/skills/app-skills.d.ts +11 -0
  177. package/dist/skills/app-skills.d.ts.map +1 -0
  178. package/dist/skills/app-skills.js +37 -0
  179. package/dist/skills/app-skills.js.map +1 -0
  180. package/dist/skills/catalog.d.ts +9 -3
  181. package/dist/skills/catalog.d.ts.map +1 -1
  182. package/dist/skills/catalog.js +35 -19
  183. package/dist/skills/catalog.js.map +1 -1
  184. package/dist/skills/service.d.ts.map +1 -1
  185. package/dist/spa-static.d.ts.map +1 -1
  186. package/dist/storage/domains/audit/actions.d.ts +6 -6
  187. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  188. package/dist/storage/domains/audit/actors.d.ts +1 -1
  189. package/dist/storage/domains/audit/actors.d.ts.map +1 -1
  190. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  191. package/dist/storage/domains/audit/domain.d.ts.map +1 -1
  192. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -1
  193. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  194. package/dist/storage/domains/comments/domain.d.ts.map +1 -1
  195. package/dist/storage/domains/comments/feed-context.d.ts.map +1 -1
  196. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  197. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
  198. package/dist/storage/domains/filesystem/base.d.ts.map +1 -1
  199. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  200. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  201. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -1
  202. package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
  203. package/dist/storage/domains/projects/base.d.ts +1 -1
  204. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  205. package/dist/storage/domains/queue-health/base.d.ts.map +1 -1
  206. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  207. package/dist/storage/domains/source-control/inmemory.d.ts +5 -5
  208. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  209. package/dist/storage/domains/work-items/base.d.ts +1 -1
  210. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  211. package/dist/storage/domains/work-items/board-version.d.ts.map +1 -1
  212. package/dist/telemetry-types.d.ts +1 -1
  213. package/dist/telemetry-types.d.ts.map +1 -1
  214. package/dist/timing.d.ts.map +1 -1
  215. package/dist/tools/session-tools.d.ts.map +1 -1
  216. package/dist/tools/session-tools.js +3 -1
  217. package/dist/tools/session-tools.js.map +1 -1
  218. package/dist/tools/sessions-state-processor.d.ts.map +1 -1
  219. package/dist/tools/work-item-tools.js +1 -1
  220. package/dist/workspace.d.ts +13 -6
  221. package/dist/workspace.d.ts.map +1 -1
  222. package/dist/workspace.js +66 -14
  223. package/dist/workspace.js.map +1 -1
  224. package/package.json +8 -8
package/README.md CHANGED
@@ -66,12 +66,12 @@ import { defineBoard } from '@mastra/factory/boards';
66
66
 
67
67
  const releaseBoard = defineBoard({
68
68
  id: 'release',
69
- title: 'Release',
70
- initialPhase: 'queued',
69
+ name: 'Release',
70
+ initialPhase: 'backlog',
71
71
  phases: {
72
- queued: { title: 'Queued', kind: 'resting', next: 'shipped' },
73
- shipped: {
74
- title: 'Shipped',
72
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['done'] },
73
+ done: {
74
+ name: 'Done',
75
75
  kind: 'terminal',
76
76
  onEnter: {
77
77
  manual: () => ({ type: 'reject', code: 'release_held', reason: 'Release is held.' }),
@@ -126,13 +126,13 @@ When a tracked GitHub, GitLab, Linear, or Jira issue or incident.io follow-up is
126
126
  ```typescript
127
127
  const releaseBoard = defineBoard({
128
128
  id: 'release',
129
- title: 'Release',
130
- initialPhase: 'queued',
131
- sourceClosed: { completed: 'shipped', canceled: 'dropped' },
129
+ name: 'Release',
130
+ initialPhase: 'backlog',
131
+ sourceClosed: { completed: 'done', canceled: 'dropped' },
132
132
  phases: {
133
- queued: { title: 'Queued', kind: 'resting', outcomes: { ship: 'shipped', drop: 'dropped' } },
134
- shipped: { title: 'Shipped', kind: 'terminal' },
135
- dropped: { title: 'Dropped', kind: 'terminal' },
133
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['done', 'dropped'] },
134
+ done: { name: 'Done', kind: 'terminal' },
135
+ dropped: { name: 'Dropped', kind: 'terminal' },
136
136
  },
137
137
  });
138
138
  ```
@@ -148,17 +148,17 @@ import { defineBoard } from '@mastra/factory/boards';
148
148
  import type { BoardToolResultRuleHandler } from '@mastra/factory/boards';
149
149
 
150
150
  const shipIt: BoardToolResultRuleHandler = context => {
151
- if (context.result.status !== 'success' || context.item.stages[0] !== 'queued') return;
151
+ if (context.result.status !== 'success' || context.item.stages[0] !== 'backlog') return;
152
152
  return { type: 'notify', idempotencyKey: `${context.ingress.id}:shipped`, title: 'Release shipped' };
153
153
  };
154
154
 
155
155
  const releaseBoard = defineBoard({
156
156
  id: 'release',
157
- title: 'Release',
158
- initialPhase: 'queued',
157
+ name: 'Release',
158
+ initialPhase: 'backlog',
159
159
  phases: {
160
- queued: { title: 'Queued', kind: 'resting', next: 'shipped' },
161
- shipped: { title: 'Shipped', kind: 'terminal' },
160
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['done'] },
161
+ done: { name: 'Done', kind: 'terminal' },
162
162
  },
163
163
  tools: { ship_it: { onResult: shipIt } },
164
164
  });
@@ -166,7 +166,7 @@ const releaseBoard = defineBoard({
166
166
 
167
167
  The handler receives the bound item, actor, board, tool name, normalized result, and `configVersion`, and returns one decision or `undefined`. Tool names follow the identifier rules for decision roles; `onResult` must be a function and the leaf may contain nothing else. Violations are `BoardDefinitionError`s at definition time.
168
168
 
169
- The default Work board declares one rule: `submit_plan`. In an interactive session, a successful result whose content starts with `Plan approved.` transitions a Planning card to Execute when the actor is an agent in the `plan` role. Review declares none. Rules resolve from the installed board, so a replacement `work` board can declare its own `tools.submit_plan.onResult`. Resolution is fail-closed: a tool result on a card whose board is not installed, or whose board does not declare that tool, fires no rule. Other boards inherit nothing from Work even if they reuse its phase names.
169
+ The default Work board declares two rules. `submit_plan`: in an interactive session, a successful result whose content starts with `Plan approved.` transitions a Planning card to Execute when the actor is the planning agent (`work`, or `plan` for cards planned before Planning moved to `work`). `factory_record_review_verdict` tells the builder to open the change request once self-review approves. Review declares none. Rules resolve from the installed board, so a replacement `work` board can declare its own `tools.submit_plan.onResult`. Resolution is fail-closed: a tool result on a card whose board is not installed, or whose board does not declare that tool, fires no rule. Other boards inherit nothing from Work even if they reuse its phase names.
170
170
 
171
171
  ### Board event rules
172
172
 
@@ -177,7 +177,7 @@ import { defineBoard } from '@mastra/factory';
177
177
 
178
178
  const deliveryBoard = defineBoard({
179
179
  id: 'delivery',
180
- title: 'Delivery',
180
+ name: 'Delivery',
181
181
  initialPhase: 'triage',
182
182
  phases: {/* ... */},
183
183
  events: {
@@ -192,7 +192,7 @@ const deliveryBoard = defineBoard({
192
192
  },
193
193
  linear: {
194
194
  issueClosed: context =>
195
- context.issue.stateType === 'canceled' && context.phase?.outcomes.includes('canceled')
195
+ context.issue.stateType === 'canceled' && context.phase?.nextPhases.includes('canceled')
196
196
  ? {
197
197
  type: 'transition',
198
198
  idempotencyKey: `${context.ingress.id}:canceled`,
@@ -215,7 +215,7 @@ For each event, Factory picks the handler in this order:
215
215
 
216
216
  A board handler that returns a decision replaces the default for that card. One that returns nothing lets the default run, so card-independent defaults such as dismissing stale Factory reviews still apply. The stock Work and Review behavior stays in the integration defaults and never acts on cards on other boards, so a custom board needs no board guards and no delegation to stock handlers.
217
217
 
218
- Every integration rule context bound to a card carries `phase`: `{ id, kind, role?, outcomes }`, describing what the card's current phase means on its board. `outcomes` lists the outcome names the phase declares. `phase` is absent when the card sits in more than one stage or its board does not declare the phase.
218
+ Every integration rule context bound to a card carries `phase`: `{ id, kind, context?, nextPhases, outcomes }`, describing what the card's current phase means on its board. `context` is the session context of a working phase (`role` carries the same value and is deprecated), `nextPhases` lists the phases the card may move to, and `outcomes` lists the outcome names the phase declares, if any. `phase` is absent when the card sits in more than one stage or its board does not declare the phase.
219
219
 
220
220
  Unknown integrations, unknown event names, and handler values other than a function or `null` are rejected by `defineBoard` with a `BoardDefinitionError`.
221
221
 
@@ -236,7 +236,7 @@ new MastraFactory({ storage, rules: defaultFactoryRules({ version: 'v2', overrid
236
236
  new MastraFactory({ storage, configVersion: 'v2', boards: [defineBoard({ ..., tools: { my_tool: { onResult } } })] });
237
237
  ```
238
238
 
239
- Contexts that carried `ruleSetVersion` now carry `configVersion`. To customize Work's `submit_plan` rule, declare `tools.submit_plan.onResult` on the installed replacement `work` board.
239
+ Contexts that carried `ruleSetVersion` now carry `configVersion`. To add a tool-result rule to Work, declare it under `tools` on the installed replacement `work` board.
240
240
 
241
241
  ### Board transition policy
242
242
 
@@ -249,19 +249,19 @@ import { defineBoard } from '@mastra/factory/boards';
249
249
  import type { BoardTransitionPolicy } from '@mastra/factory/boards';
250
250
 
251
251
  const releasePolicy: BoardTransitionPolicy = context => {
252
- if (context.toStage === 'shipped' && !context.isHumanTransition) {
252
+ if (context.toStage === 'build' && !context.isHumanTransition) {
253
253
  return { type: 'reject', code: 'approval_required', reason: 'A person must approve this release.' };
254
254
  }
255
255
  };
256
256
 
257
257
  const releaseBoard = defineBoard({
258
258
  id: 'release',
259
- title: 'Release',
259
+ name: 'Release',
260
260
  initialPhase: 'approval',
261
261
  transitionPolicy: releasePolicy,
262
262
  phases: {
263
- approval: { title: 'Approval', kind: 'resting', next: 'shipped' },
264
- shipped: { title: 'Shipped', kind: 'working', role: 'release' },
263
+ approval: { name: 'Approval', kind: 'resting', nextPhases: ['build'] },
264
+ build: { name: 'Build', kind: 'working', runner: { context: 'release' } },
265
265
  },
266
266
  });
267
267
  // Install through new MastraFactory({ storage, boards: [releaseBoard] }).
@@ -280,47 +280,148 @@ A policy cannot bypass topology, ingress authorization, board ownership, externa
280
280
  Every phase declares what it _is_ with a required `kind`; `defineBoard()` rejects a phase without one.
281
281
 
282
282
  - `resting` — the card is parked. A human move out of a resting phase arms autonomy; a move back into one disarms it. `initialPhase` must be resting: a card cannot arrive already seated or already finished.
283
- - `working` — an agent seat carries the card. `role` is required (same identifier rules as decision roles) and names the seat a human kickoff opens and the lane a rule-started run leaves rest for. Two working phases may share a role; `phaseForRole` returns the first in declaration order.
284
- - `terminal` — the card is finished. Entering it releases the sandbox and lets sweeps supersede stale decisions and revoke run bindings. `role` is not allowed.
283
+ - `working` — an agent carries the card. `runner.context` (same identifier rules as decision roles, default `work`) names the session context a human kickoff opens and the lane a rule-started run leaves rest for. Two working phases may share a context, and then a session; `phaseForRole` returns the first in declaration order.
284
+ - `terminal` — the card is finished. Entering it releases the sandbox and lets sweeps supersede stale decisions and revoke run bindings. `runner` is not allowed.
285
285
 
286
286
  ```typescript
287
287
  const releaseBoard = defineBoard({
288
288
  id: 'release',
289
- title: 'Release',
290
- initialPhase: 'queued',
289
+ name: 'Release',
290
+ initialPhase: 'backlog',
291
291
  phases: {
292
- queued: { title: 'Queued', kind: 'resting', next: 'shipping' },
293
- shipping: { title: 'Shipping', kind: 'working', role: 'release', next: 'shipped' },
294
- shipped: { title: 'Shipped', kind: 'terminal' },
292
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['build'] },
293
+ build: { name: 'Build', kind: 'working', runner: { context: 'release' }, nextPhases: ['done'] },
294
+ done: { name: 'Done', kind: 'terminal' },
295
295
  },
296
296
  });
297
297
  ```
298
298
 
299
- Work declares `intake` resting; `triage`, `planning`, `execute`, and `review` working with roles `triage`, `plan`, `work`, and `work`; `done` and `canceled` terminal. Review declares `intake` resting, `review` working with role `review`, and `done`/`canceled` terminal. The definition exposes the derived helpers `phaseKind`, `isWorking`, `isTerminal`, `roleForPhase`, and `phaseForRole`.
299
+ Work declares `intake` resting; `triage`, `planning`, `execute`, and `review` working with contexts `triage`, `work`, `work`, and `work`; `done` and `canceled` terminal. Review declares `intake` resting, `review` working with context `review`, and `done`/`canceled` terminal. The definition exposes the derived helpers `phaseKind`, `isWorking`, `isTerminal`, `roleForPhase`, and `phaseForRole`.
300
+
301
+ Planning and Building on Work share the `work` session, so the builder keeps the planner's reasoning, branch and sandbox. Self-review still runs in its own `review` session. When the planner moves the card to Building, the build prompt is queued onto its own run, and the planner's run binding ends at that point: Factory tools it calls later in the same turn are rejected. When the builder moves a card back from Building to Planning, no new planning run starts; the builder keeps working in its run. When a person moves it back, Planning starts again, in the same `work` session. The old `plan` role still names Planning: a run requested or queued for `plan` starts Planning in a `plan` session, and the build then continues in that session. Cards planned before this change do the same.
300
302
 
301
303
  Consent, the external-author guard, kickoff seating, run-start lanes, terminal cleanup, and the closed-PR and issue sweeps all read the installed board's declarations; nothing name-matches phases. A board that reuses Work's phase names gets exactly what it declared. Persisted `board` is authoritative; rows without one are read as Review for pull requests and Work otherwise.
302
304
 
303
305
  Unknown semantics fail closed. When the board is not installed or the phase is not declared: external-event transitions ask for consent, no sweep or cleanup treats the card as finished, and a rule-started run from rest with no lane for its role is rejected.
304
306
 
305
- **Migration:** Existing `defineBoard()` calls must add `kind` to every phase and `role` to working phases.
307
+ **Migration:** Existing `defineBoard()` calls must add `kind` to every phase. Working phases name their session context with `runner.context`.
306
308
 
307
309
  ```typescript
308
310
  // before
309
- phases: { queued: { title: 'Queued', next: 'shipped' }, shipped: { title: 'Shipped' } }
311
+ phases: { backlog: { title: 'Backlog', next: 'done' }, done: { title: 'Done' } }
310
312
  // after
311
313
  phases: {
312
- queued: { title: 'Queued', kind: 'resting', next: 'shipped' },
313
- shipped: { title: 'Shipped', kind: 'terminal' },
314
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['done'] },
315
+ done: { name: 'Done', kind: 'terminal' },
314
316
  }
315
317
  ```
316
318
 
317
319
  **Remaining limitations:** Throughput and lead-time metrics still count completions by Work's `done` phase. `factory-ui` still renders the built-in stage and role pipeline.
318
320
 
321
+ ### Phase runners, automatic starts and exit tools
322
+
323
+ Boards and phases take `name` and an optional `description`. A phase lists the phases a card may move to in `nextPhases`. A working phase can also declare:
324
+
325
+ - `runner` — `{ agentId?, skill?, context?, instructions? }`. `context` names the session the phase runs in (defaults to `work`); phases that share a context share a session. When the agent moves its own card into another phase of its context, that phase's `skill` still starts: its prompt is queued onto the agent's run. `skill` starts that skill when a card enters the phase from a source the phase has no `onEnter` handler for. It names a built-in Factory skill or one of your app's skills (see [Skills for custom boards](#skills-for-custom-boards)). `instructions` (up to 1,000 characters) are added to every run the phase starts. `agentId` defaults to `code-agent`, the only agent Factory runs today.
326
+ - `autoStart` and `autoStartWhen` — the phase's runs start without a person's approval, for every card or only for cards matching the rule. Cards from outside the write-access circle still wait for a person.
327
+ - `exitTool` — a tool name, or `{ id, autoApprove, autoApproveWhen }`, that ends the phase. The factory-phase signal tells the agent the phase ends when that tool succeeds and lists `nextPhases`; the agent then moves the card itself with `factory_transition_work_item`, so it picks where the card goes. The phase needs at least one next phase. `autoApprove` answers the person's approval the tool waits for, for every card or only cards matching `autoApproveWhen`. Factory sessions already run tools without asking, so today this matters for `submit_plan`: an auto-approved plan is accepted without a person. `defineBoard()` accepts `autoApprove` on any exit tool, and the catalog reports it, but on other tools it has no effect yet.
328
+
329
+ Rules test a card's facts: `item.labels`, `item.title`, `item.author`, `source.kind` and `source.integration`.
330
+
331
+ ```typescript
332
+ const LOW_EFFORT = {
333
+ operator: 'AND',
334
+ conditions: [{ field: 'item.labels', operator: 'contains', value: 'effort:low' }],
335
+ } as const;
336
+
337
+ const releaseBoard = defineBoard({
338
+ id: 'release',
339
+ name: 'Release',
340
+ initialPhase: 'backlog',
341
+ phases: {
342
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['plan'] },
343
+ plan: {
344
+ name: 'Plan',
345
+ kind: 'working',
346
+ runner: { context: 'build', skill: 'factory-plan' },
347
+ autoStart: true,
348
+ autoStartWhen: LOW_EFFORT,
349
+ exitTool: { id: 'submit_plan', autoApprove: true },
350
+ nextPhases: ['build', 'canceled'],
351
+ },
352
+ build: { name: 'Build', kind: 'working', runner: { context: 'build' }, nextPhases: ['done'] },
353
+ done: { name: 'Done', kind: 'terminal' },
354
+ canceled: { name: 'Canceled', kind: 'terminal' },
355
+ },
356
+ });
357
+ ```
358
+
359
+ **Migration:** `defineBoard()` still accepts the old field names and maps them to the new ones straight away: `title` → `name`, `role` → `runner.context`, `next` → `nextPhases`. It logs one warning per board that still uses them. Setting both `title` and `name`, or `role` and `runner.context`, to different values is rejected. `outcomes` is not deprecated: its targets are added to `nextPhases` and outcome names keep working. The board `defineBoard()` returns, and the one `new MastraFactory({ boards })` takes, has only the new fields, so code that reads `board.title`, `phase.title`, `phase.role` or `phase.next` must switch to `name`, `runner.context` and `nextPhases`. The board catalog (`GET /web/factory/projects/:id/boards`) still sends `title` and `role` beside the new fields for older clients. Card session keys do not change: a phase's context is its old role.
360
+
361
+ ### Skills for custom boards
362
+
363
+ A phase's `runner.skill` runs a skill when a card enters the phase. Use a built-in skill such as `factory-plan`, or write your own. Factory finds a skill in this order; the first match wins:
364
+
365
+ 1. **Code skills** — passed to `new MastraFactory({ skills })`, created with `createSkill()` from `@mastra/core/skills`.
366
+ 2. **Folder skills** — `src/mastra/factory-skills/<name>/SKILL.md` in your app. `mastra build` copies the folder into the build output. The older `src/mastra/public/factory-skills` still works; when both exist, `src/mastra/factory-skills` wins. Factory also looks in `public/factory-skills`, `factory-skills` and `output/factory-skills` under the working directory, and uses the first that exists. Supporting files, such as `references/`, can sit next to `SKILL.md`.
367
+ 3. **Built-in skills** shipped with `@mastra/factory`.
368
+
369
+ A skill with the same name as a built-in skill replaces it, for the built-in boards too. A code skill replaces the whole folder skill of the same name, including its supporting files. Repository skills in a project's own repository never replace these.
370
+
371
+ Skill names use 1–64 lowercase letters, digits and single hyphens, such as `release-notes`. A folder skill's `name` must match its directory name. Names in `skills` must be unique, or the constructor throws.
372
+
373
+ ```typescript
374
+ import { createSkill } from '@mastra/core/skills';
375
+ import { MastraFactory } from '@mastra/factory';
376
+ import { defineBoard } from '@mastra/factory/boards';
377
+
378
+ const releaseNotes = createSkill({
379
+ name: 'release-notes',
380
+ description: 'Draft release notes for a work item',
381
+ instructions: 'Group the release notes by package and end with a one-line summary. Then move the card to Published.',
382
+ });
383
+
384
+ const notesBoard = defineBoard({
385
+ id: 'notes',
386
+ name: 'Notes',
387
+ initialPhase: 'backlog',
388
+ phases: {
389
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['drafting'] },
390
+ drafting: {
391
+ name: 'Drafting',
392
+ kind: 'working',
393
+ runner: { context: 'writer', skill: 'release-notes' },
394
+ nextPhases: ['published'],
395
+ },
396
+ published: { name: 'Published', kind: 'terminal' },
397
+ },
398
+ });
399
+
400
+ const factory = new MastraFactory({ ...config, boards: [notesBoard], skills: [releaseNotes] });
401
+ ```
402
+
403
+ The same skill as a folder skill is `src/mastra/factory-skills/release-notes/SKILL.md`:
404
+
405
+ ```markdown
406
+ ---
407
+ name: release-notes
408
+ description: Draft release notes for a work item
409
+ ---
410
+
411
+ Group the release notes by package and end with a one-line summary. Then move the card to Published.
412
+ ```
413
+
414
+ The skill receives the card's link, or its title when it has none, as arguments. Every Factory session can also load your skills on request, like the built-in ones.
415
+
416
+ When a card enters the phase and the skill does not exist, no run starts and the card stays in the phase. The start fails with `Skill not found: <name>.`; after a few quick retries it stays failed, and you can retry it once the skill exists. The constructor logs a warning for each phase that names a missing skill. It also warns when a phase outside the `review` context runs a review skill (`factory-review`, `factory-rereview`, `factory-self-review`, `factory-gitlab-review`, `factory-gitlab-rereview`), because sessions only load those for reviews.
417
+
418
+ Settings › Skills shows the skill each custom phase runs, marks a phase whose skill is missing, marks built-in skills your app replaces, and lists your other skills under **App skills**.
419
+
319
420
  ### Execute a custom board
320
421
 
321
422
  Installed custom boards can create linked items, start working roles, transition through bound tools, and react to completed tool results. Board and phase identifiers contain 1–128 letters, digits, underscores, or hyphens and start with a letter or digit. Identifiers are case-sensitive and cannot contain surrounding whitespace.
322
423
 
323
- This configuration runs a release rehearsal through `queued → preparing → shipping → shipped`. Add it to an existing Factory host with configured storage, a GitHub integration, a connected project repository, a sandbox, and organization-scoped model credentials. Enable automatic runs for the project. The repository must contain a `release:check` package script; it should validate the release without publishing it.
424
+ This configuration runs a release rehearsal through `backlog → preparing → build → done`. Add it to an existing Factory host with configured storage, a GitHub integration, a connected project repository, a sandbox, and organization-scoped model credentials. Enable automatic runs for the project. The repository must contain a `release:check` package script; it should validate the release without publishing it.
324
425
 
325
426
  ```typescript
326
427
  import { MastraFactory } from '@mastra/factory';
@@ -328,14 +429,14 @@ import type { MastraFactoryConfig } from '@mastra/factory';
328
429
  import { defineBoard } from '@mastra/factory/boards';
329
430
  import type { BoardPhaseDefinition } from '@mastra/factory/boards';
330
431
 
331
- type ReleasePhase = 'queued' | 'preparing' | 'shipping' | 'shipped';
432
+ type ReleasePhase = 'backlog' | 'preparing' | 'build' | 'done';
332
433
 
333
434
  const releaseBoard = defineBoard<'release', Record<ReleasePhase, BoardPhaseDefinition<ReleasePhase>>>({
334
435
  id: 'release',
335
- title: 'Release',
336
- initialPhase: 'queued',
436
+ name: 'Release',
437
+ initialPhase: 'backlog',
337
438
  transitionPolicy: context => {
338
- if (context.fromStage === 'queued' && context.toStage === 'preparing' && !context.isHumanTransition) {
439
+ if (context.fromStage === 'backlog' && context.toStage === 'preparing' && !context.isHumanTransition) {
339
440
  return {
340
441
  type: 'reject',
341
442
  code: 'approval_required',
@@ -344,27 +445,27 @@ const releaseBoard = defineBoard<'release', Record<ReleasePhase, BoardPhaseDefin
344
445
  }
345
446
  },
346
447
  phases: {
347
- queued: { title: 'Queued', kind: 'resting', next: 'preparing' },
448
+ backlog: { name: 'Backlog', kind: 'resting', nextPhases: ['preparing'] },
348
449
  preparing: {
349
- title: 'Preparing',
450
+ name: 'Preparing',
350
451
  kind: 'working',
351
- role: 'release-preparer',
352
- next: 'shipping',
452
+ runner: { context: 'release-preparer' },
453
+ nextPhases: ['build'],
353
454
  onEnter: {
354
455
  issue: context => ({
355
456
  type: 'invokeSkill',
356
457
  idempotencyKey: `${context.ingress.id}:prepare`,
357
458
  role: 'release-preparer',
358
459
  prompt:
359
- 'Inspect the release changes. When ready, call factory_transition_work_item with stage shipping and the current expectedRevision from the Factory phase signal.',
460
+ 'Inspect the release changes. When ready, call factory_transition_work_item with stage build and the current expectedRevision from the Factory phase signal.',
360
461
  }),
361
462
  },
362
463
  },
363
- shipping: {
364
- title: 'Shipping',
464
+ build: {
465
+ name: 'Build',
365
466
  kind: 'working',
366
- role: 'release-publisher',
367
- next: 'shipped',
467
+ runner: { context: 'release-publisher' },
468
+ nextPhases: ['done'],
368
469
  onEnter: {
369
470
  issue: context => ({
370
471
  type: 'invokeSkill',
@@ -375,13 +476,13 @@ const releaseBoard = defineBoard<'release', Record<ReleasePhase, BoardPhaseDefin
375
476
  }),
376
477
  },
377
478
  },
378
- shipped: { title: 'Shipped', kind: 'terminal' },
479
+ done: { name: 'Done', kind: 'terminal' },
379
480
  },
380
481
  tools: {
381
482
  execute_command: {
382
483
  onResult: context => {
383
484
  if (
384
- context.item.stages[0] !== 'shipping' ||
485
+ context.item.stages[0] !== 'build' ||
385
486
  context.result.status !== 'success' ||
386
487
  typeof context.result.value !== 'string' ||
387
488
  !context.result.value.trimEnd().endsWith('RELEASE_CHECK_PASSED')
@@ -392,7 +493,7 @@ const releaseBoard = defineBoard<'release', Record<ReleasePhase, BoardPhaseDefin
392
493
  type: 'transition',
393
494
  idempotencyKey: `${context.ingress.id}:checked`,
394
495
  board: 'release',
395
- stage: 'shipped',
496
+ stage: 'done',
396
497
  };
397
498
  },
398
499
  },
@@ -406,7 +507,7 @@ export function createFactory(config: Omit<MastraFactoryConfig, 'boards' | 'incl
406
507
 
407
508
  Use the host's normal `prepare()`, `new Mastra(...)`, and `finalize()` sequence. Change the hardcoded `includeDefaultBoards: true` in `createFactory` to `false` to run without Work or Review. Working roles name bindings on the shared Code Agent; they do not register separate agents. Factory has no per-role agent configuration option. The board's kickoff prompts supply the role-specific instructions.
408
509
 
409
- Create a card with `POST /web/factory/projects/:id/work-items`, passing `board: 'release'`, a title, and the GitHub issue's `externalSource`. The card starts in `queued`. Then use `POST /web/factory/projects/:id/work-items/:workItemId/transition` with `board: 'release'`, `stage: 'preparing'`, the returned `expectedRevision`, a unique `requestId`, and a `cause`. Send these requests as an authorized user using the host's authentication. The human transition satisfies this board's policy and starts the preparer. Its bound tool advances to `shipping`; the publisher's completed check produces a deferred transition to `shipped`. Terminal entry revokes the binding and releases the sandbox. The Factory UI performs the same operations against installed boards (see below); verify the persisted card's board, phase, and deferred decision status either way.
510
+ Create a card with `POST /web/factory/projects/:id/work-items`, passing `board: 'release'`, a title, and the GitHub issue's `externalSource`. The card starts in `backlog`. Then use `POST /web/factory/projects/:id/work-items/:workItemId/transition` with `board: 'release'`, `stage: 'preparing'`, the returned `expectedRevision`, a unique `requestId`, and a `cause`. Send these requests as an authorized user using the host's authentication. The human transition satisfies this board's policy and starts the preparer. Its bound tool advances to `build`; the publisher's completed check produces a deferred transition to `done`. Terminal entry revokes the binding and releases the sandbox. The Factory UI performs the same operations against installed boards (see below); verify the persisted card's board, phase, and deferred decision status either way.
410
511
 
411
512
  Lifecycle, tool-result, and integration handlers may return `upsertLinkedWorkItem` targeting a different installed board. Linked cards enter that board's declared initial phase before moving to the requested phase. Existing cards cannot be reassigned to another board through either decision type. Targets are validated against their own installed board before acceptance and again before uncommitted deferred effects execute; a phase declared only on another board is not valid. Committed replay retains its recorded result and original `configVersion`.
412
513
 
@@ -414,12 +515,12 @@ Custom phase signals and persisted tool-result ingestion use the item's installe
414
515
 
415
516
  ### Custom boards in the Factory UI
416
517
 
417
- `GET /web/factory/projects/:id/boards` returns the installed board catalog for an authorized project: each board's `id`, `title`, `initialPhase`, declaration-ordered `phases` (with `kind` and working `role`), and transition topology. Handlers, policies, and prompts are never serialized. The Factory UI reads this catalog instead of assuming Work and Review:
518
+ `GET /web/factory/projects/:id/boards` returns the installed board catalog for an authorized project: each board's `id`, `title`, `initialPhase`, declaration-ordered `phases` (with `kind`, and for working phases `role` and the `skill` the runner starts), and transition topology. Handlers, policies, and prompts are never serialized. The Factory UI reads this catalog instead of assuming Work and Review:
418
519
 
419
520
  - Installed boards appear in the sidebar — built-ins first, then custom boards in registry order. Work and Review keep `/factories/:id/work` and `/factories/:id/review`; custom boards open at `/factories/:id/boards/:boardId`. An unknown board ID or a failed catalog request shows an explicit unavailable state rather than falling back to Work.
420
521
  - Columns, phase labels, and working/terminal presentation come from the phase definitions. Card creation sends the selected `board`, so the server picks the initial phase. Card menus offer only moves the topology declares; the server still enforces policy, authorization, and revisions.
421
522
  - A card's persisted `board` decides which board it belongs to. UI actions cannot move a card to a different board.
422
- - Settings › Skills groups built-in skills under Work and Review and lists each custom board's declared roles. Custom-board kickoff instructions live in code; there is no skill or per-role agent configuration for them.
523
+ - Settings › Skills groups built-in skills under Work and Review. For each custom board it shows the skill each phase runs (`runner.skill`) and the roles of phases that run no skill; those phases' kickoff instructions live in the board definition. Your app's other skills appear under App skills.
423
524
  - Not yet board-aware: the Overview funnel and stage-hold metrics (Work-only), search scopes (Work/Review sessions), and the automation toggles that are specific to Work triage and Review. Custom-board cards still count toward in-flight totals and appear in activity and audit logs.
424
525
 
425
526
  ### Route intake to custom boards
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { RuleFacts } from '../rules/rule-group.js';
2
+ import type { WorkItemRow } from '../storage/domains/work-items/base.js';
3
+ /** What a phase condition (`autoStartWhen`, `exitTool.autoApproveWhen`) can test about a card. */
4
+ export declare function cardRuleFacts(item: Pick<WorkItemRow, 'title' | 'metadata' | 'externalSource'>): RuleFacts;
5
+ //# sourceMappingURL=card-facts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-facts.d.ts","sourceRoot":"","sources":["../../src/boards/card-facts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAEzE,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,UAAU,GAAG,gBAAgB,CAAC,GAAG,SAAS,CAiBzG"}
@@ -0,0 +1,19 @@
1
+ // src/boards/card-facts.ts
2
+ /** What a phase condition (`autoStartWhen`, `exitTool.autoApproveWhen`) can test about a card. */
3
+ function cardRuleFacts(item) {
4
+ const metadata = item.metadata ?? {};
5
+ const labels = Array.isArray(metadata.labels) ? metadata.labels.filter((label) => typeof label === "string") : [];
6
+ const source = item.externalSource;
7
+ return {
8
+ "item.labels": labels,
9
+ "item.title": item.title,
10
+ ...typeof metadata.author === "string" ? { "item.author": metadata.author } : {},
11
+ ...source ? {
12
+ "source.kind": source.type === "pull-request" ? "pull_request" : source.type,
13
+ "source.integration": source.integrationId
14
+ } : {}
15
+ };
16
+ }
17
+ export { cardRuleFacts };
18
+
19
+ //# sourceMappingURL=card-facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-facts.js","names":[],"sources":["../../src/boards/card-facts.ts"],"sourcesContent":["import type { RuleFacts } from '../rules/rule-group.js';\nimport type { WorkItemRow } from '../storage/domains/work-items/base.js';\n\n/** What a phase condition (`autoStartWhen`, `exitTool.autoApproveWhen`) can test about a card. */\nexport function cardRuleFacts(item: Pick<WorkItemRow, 'title' | 'metadata' | 'externalSource'>): RuleFacts {\n const metadata = item.metadata ?? {};\n const labels = Array.isArray(metadata.labels)\n ? metadata.labels.filter((label): label is string => typeof label === 'string')\n : [];\n const source = item.externalSource;\n return {\n 'item.labels': labels,\n 'item.title': item.title,\n ...(typeof metadata.author === 'string' ? { 'item.author': metadata.author } : {}),\n ...(source\n ? {\n 'source.kind': source.type === 'pull-request' ? 'pull_request' : source.type,\n 'source.integration': source.integrationId,\n }\n : {}),\n };\n}\n"],"mappings":";;AAIA,SAAgB,cAAc,MAA6E;CACzG,MAAM,WAAW,KAAK,YAAY,CAAC;CACnC,MAAM,SAAS,MAAM,QAAQ,SAAS,MAAM,IACxC,SAAS,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IAC5E,CAAC;CACL,MAAM,SAAS,KAAK;CACpB,OAAO;EACL,eAAe;EACf,cAAc,KAAK;EACnB,GAAI,OAAO,SAAS,WAAW,WAAW,EAAE,eAAe,SAAS,OAAO,IAAI,CAAC;EAChF,GAAI,SACA;GACE,eAAe,OAAO,SAAS,iBAAiB,iBAAiB,OAAO;GACxE,sBAAsB,OAAO;EAC/B,IACA,CAAC;CACP;AACF"}
@@ -1,3 +1,4 @@
1
+ import type { RuleFacts, RuleGroup } from '../rules/rule-group.js';
1
2
  import type { FactoryGithubEventName, FactoryGithubRuleContext, FactoryGitLabEventName, FactoryGitLabRuleContext, FactoryIncidentioEventName, FactoryIncidentioRuleContext, FactoryJiraEventName, FactoryJiraRuleContext, FactoryLinearEventName, FactoryLinearRuleContext, FactoryRuleHandler, FactoryRuleSource, FactoryStageRuleContext, FactoryToolResultRuleContext } from '../rules/types.js';
2
3
  import type { BoardTransitionPolicy } from './transition-policy.js';
3
4
  type BoardPhaseHandlers = Partial<Record<FactoryRuleSource, FactoryRuleHandler<FactoryStageRuleContext>>>;
@@ -33,26 +34,112 @@ type ReadonlyFactoryBoardRules = Readonly<Record<string, Readonly<Partial<Record
33
34
  /**
34
35
  * What a phase means to the runtime.
35
36
  * - `resting`: nobody is seated; a card waits for a person or an event.
36
- * - `working`: an agent in `role` carries the card; a person's move in arms autonomy.
37
+ * - `working`: an agent in the phase's context carries the card; a person's move in arms autonomy.
37
38
  * - `terminal`: the card is finished; sessions stop and held resources are released.
38
39
  */
39
40
  export type BoardPhaseKind = 'resting' | 'working' | 'terminal';
41
+ /** The agent Factory runs every phase with. */
42
+ export declare const FACTORY_AGENT_ID = "code-agent";
43
+ /** The session context a working phase runs in when it names none. */
44
+ export declare const DEFAULT_PHASE_CONTEXT = "work";
45
+ /** Who runs a working phase, and with what. */
46
+ export interface BoardPhaseRunner {
47
+ /** Must be `code-agent`, the agent Factory runs; omitted means that agent. */
48
+ readonly agentId?: string;
49
+ /** Skill started when a card enters the phase from a source with no `onEnter` handler. */
50
+ readonly skill?: string;
51
+ /**
52
+ * The session the phase runs in. Phases naming the same context share one session per card;
53
+ * a different context gets its own. Defaults to `work`.
54
+ */
55
+ readonly context?: string;
56
+ /** Appended to every run the phase starts. */
57
+ readonly instructions?: string;
58
+ }
59
+ /**
60
+ * The tool whose successful call ends a working phase. The agent then moves the card itself with
61
+ * `factory_transition_work_item`, to one of the phase's `nextPhases`.
62
+ */
63
+ export interface BoardPhaseExitTool {
64
+ readonly id: string;
65
+ /**
66
+ * Answer the approval the tool waits for without a person. Factory sessions run other tools
67
+ * without asking, so today this applies to `submit_plan`. Defaults to false.
68
+ */
69
+ readonly autoApprove?: boolean;
70
+ /** With `autoApprove`, only cards this matches are approved without a person. */
71
+ readonly autoApproveWhen?: RuleGroup;
72
+ }
40
73
  type BoardPhaseBase<PhaseId extends string> = {
41
- readonly title: string;
74
+ readonly name?: string;
75
+ /** @deprecated Use `name`. */
76
+ readonly title?: string;
77
+ readonly description?: string;
78
+ /** Phases a card may move to from here. */
79
+ readonly nextPhases?: readonly PhaseId[];
80
+ /** @deprecated List the phase in `nextPhases`. */
42
81
  readonly next?: PhaseId;
82
+ /** Named moves rules can choose by outcome; each target is also an allowed next phase. */
43
83
  readonly outcomes?: Readonly<Record<string, PhaseId>>;
44
84
  readonly onEnter?: ReadonlyBoardPhaseHandlers;
45
85
  readonly onExit?: ReadonlyBoardPhaseHandlers;
46
86
  };
47
- export type BoardPhaseDefinition<PhaseId extends string> = (BoardPhaseBase<PhaseId> & {
48
- readonly kind: 'resting';
87
+ type WorkingPhaseFields = {
88
+ readonly runner?: BoardPhaseRunner;
89
+ /** @deprecated Use `runner.context`. */
90
+ readonly role?: string;
91
+ /** Runs start without anyone approving them, even with the project's auto-run off. */
92
+ readonly autoStart?: boolean;
93
+ /** With `autoStart`, only cards this matches start on their own. */
94
+ readonly autoStartWhen?: RuleGroup;
95
+ /** A bare tool name is shorthand for `{ id }`, with nothing approved on a person's behalf. */
96
+ readonly exitTool?: string | BoardPhaseExitTool;
97
+ };
98
+ type NonWorkingPhaseFields = {
99
+ readonly runner?: never;
49
100
  readonly role?: never;
50
- }) | (BoardPhaseBase<PhaseId> & {
101
+ readonly autoStart?: never;
102
+ readonly autoStartWhen?: never;
103
+ readonly exitTool?: never;
104
+ };
105
+ /** A phase as a board declares it. */
106
+ export type BoardPhaseDefinition<PhaseId extends string> = (BoardPhaseBase<PhaseId> & NonWorkingPhaseFields & {
107
+ readonly kind: 'resting';
108
+ }) | (BoardPhaseBase<PhaseId> & WorkingPhaseFields & {
51
109
  readonly kind: 'working';
52
- readonly role: string;
53
- }) | (BoardPhaseBase<PhaseId> & {
110
+ }) | (BoardPhaseBase<PhaseId> & NonWorkingPhaseFields & {
111
+ readonly kind: 'terminal';
112
+ });
113
+ type ResolvedPhaseBase<PhaseId extends string> = {
114
+ readonly name: string;
115
+ readonly description?: string;
116
+ /** Every phase a card may move to from here, including outcome targets. */
117
+ readonly nextPhases: readonly PhaseId[];
118
+ readonly outcomes?: Readonly<Record<string, PhaseId>>;
119
+ readonly onEnter?: ReadonlyBoardPhaseHandlers;
120
+ readonly onExit?: ReadonlyBoardPhaseHandlers;
121
+ };
122
+ /**
123
+ * A phase as the board resolved it: deprecated fields are replaced by their new names, so
124
+ * only the new structure leaves `defineBoard()`, and a resolved phase can be declared again.
125
+ */
126
+ export type BoardPhase<PhaseId extends string> = (ResolvedPhaseBase<PhaseId> & {
127
+ readonly kind: 'resting';
128
+ }) | (ResolvedPhaseBase<PhaseId> & {
129
+ readonly kind: 'working';
130
+ readonly runner: Readonly<BoardPhaseRunner & {
131
+ agentId: string;
132
+ context: string;
133
+ }>;
134
+ readonly autoStart?: boolean;
135
+ readonly autoStartWhen?: RuleGroup;
136
+ readonly exitTool?: Readonly<{
137
+ id: string;
138
+ autoApprove: boolean;
139
+ autoApproveWhen?: RuleGroup;
140
+ }>;
141
+ }) | (ResolvedPhaseBase<PhaseId> & {
54
142
  readonly kind: 'terminal';
55
- readonly role?: never;
56
143
  });
57
144
  export interface BoardTransition<PhaseId extends string> {
58
145
  readonly outcome: string | null;
@@ -68,9 +155,10 @@ export type BoardSourceClosed<PhaseId extends string> = Readonly<{
68
155
  }>;
69
156
  export interface BoardDefinition<BoardId extends string, PhaseId extends string> {
70
157
  readonly id: BoardId;
71
- readonly title: string;
158
+ readonly name: string;
159
+ readonly description?: string;
72
160
  readonly initialPhase: PhaseId;
73
- readonly phases: Readonly<Record<PhaseId, BoardPhaseDefinition<PhaseId>>>;
161
+ readonly phases: Readonly<Record<PhaseId, BoardPhase<PhaseId>>>;
74
162
  readonly transitions: Readonly<Record<PhaseId, readonly BoardTransition<PhaseId>[]>>;
75
163
  readonly rules: ReadonlyFactoryBoardRules;
76
164
  /** Tool-result rules keyed by tool name; empty when the board declares none. */
@@ -85,14 +173,22 @@ export interface BoardDefinition<BoardId extends string, PhaseId extends string>
85
173
  phaseKind(phase: string): BoardPhaseKind | undefined;
86
174
  isWorking(phase: string): boolean;
87
175
  isTerminal(phase: string): boolean;
88
- /** Role seated in a working phase; undefined for resting/terminal/unknown phases. */
176
+ /** Session context of a working phase; undefined for resting/terminal/unknown phases. */
89
177
  roleForPhase(phase: string): string | undefined;
90
- /** First working phase, in declaration order, carried by `role`. */
178
+ /** First working phase, in declaration order, running in `role`'s context. */
91
179
  phaseForRole(role: string): PhaseId | undefined;
180
+ /**
181
+ * Whether the phase's runs start without anyone approving them, for a card with these facts
182
+ * (`cardRuleFacts`). A phase with a start condition never starts on its own without facts.
183
+ */
184
+ startsAutomatically(phase: string, facts?: RuleFacts): boolean;
92
185
  }
93
186
  type BoardConfig<BoardId extends string, Phases extends Record<string, BoardPhaseDefinition<keyof Phases & string>>> = {
94
187
  id: BoardId;
95
- title: string;
188
+ name?: string;
189
+ /** @deprecated Use `name`. */
190
+ title?: string;
191
+ description?: string;
96
192
  initialPhase: keyof Phases & string;
97
193
  phases: Phases;
98
194
  tools?: Record<string, BoardToolRule>;