@mastra/factory 0.21.0-alpha.6 → 0.21.0-alpha.8

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 (131) hide show
  1. package/README.md +72 -1
  2. package/dist/boards/define-board.d.ts +36 -1
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +46 -0
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/index.d.ts +2 -2
  7. package/dist/boards/index.d.ts.map +1 -1
  8. package/dist/boards/index.js +2 -2
  9. package/dist/boards/index.js.map +1 -1
  10. package/dist/boards/semantics.d.ts +34 -1
  11. package/dist/boards/semantics.d.ts.map +1 -1
  12. package/dist/boards/semantics.js +65 -1
  13. package/dist/boards/semantics.js.map +1 -1
  14. package/dist/boards/work.d.ts.map +1 -1
  15. package/dist/boards/work.js +5 -1
  16. package/dist/boards/work.js.map +1 -1
  17. package/dist/factory.d.ts.map +1 -1
  18. package/dist/factory.js +8 -0
  19. package/dist/factory.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/integrations/github/default-rules.d.ts +2 -2
  23. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  24. package/dist/integrations/github/default-rules.js +11 -8
  25. package/dist/integrations/github/default-rules.js.map +1 -1
  26. package/dist/integrations/github/integration.d.ts +18 -0
  27. package/dist/integrations/github/integration.d.ts.map +1 -1
  28. package/dist/integrations/github/integration.js +53 -2
  29. package/dist/integrations/github/integration.js.map +1 -1
  30. package/dist/integrations/github/routes.d.ts +12 -0
  31. package/dist/integrations/github/routes.d.ts.map +1 -1
  32. package/dist/integrations/github/routes.js +63 -1
  33. package/dist/integrations/github/routes.js.map +1 -1
  34. package/dist/integrations/github/rules.d.ts +5 -2
  35. package/dist/integrations/github/rules.d.ts.map +1 -1
  36. package/dist/integrations/github/rules.js +72 -19
  37. package/dist/integrations/github/rules.js.map +1 -1
  38. package/dist/integrations/gitlab/default-rules.d.ts +3 -3
  39. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -1
  40. package/dist/integrations/gitlab/default-rules.js +13 -6
  41. package/dist/integrations/gitlab/default-rules.js.map +1 -1
  42. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/rules.js +39 -8
  44. package/dist/integrations/gitlab/rules.js.map +1 -1
  45. package/dist/integrations/incidentio/default-rules.d.ts +3 -3
  46. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -1
  47. package/dist/integrations/incidentio/default-rules.js +8 -6
  48. package/dist/integrations/incidentio/default-rules.js.map +1 -1
  49. package/dist/integrations/incidentio/rules.d.ts.map +1 -1
  50. package/dist/integrations/incidentio/rules.js +6 -3
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/jira/default-rules.d.ts +3 -3
  53. package/dist/integrations/jira/default-rules.d.ts.map +1 -1
  54. package/dist/integrations/jira/default-rules.js +8 -6
  55. package/dist/integrations/jira/default-rules.js.map +1 -1
  56. package/dist/integrations/jira/rules.d.ts.map +1 -1
  57. package/dist/integrations/jira/rules.js +6 -3
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/default-rules.d.ts +3 -3
  60. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  61. package/dist/integrations/linear/default-rules.js +8 -6
  62. package/dist/integrations/linear/default-rules.js.map +1 -1
  63. package/dist/integrations/linear/routes.js +1 -1
  64. package/dist/integrations/linear/rules.d.ts.map +1 -1
  65. package/dist/integrations/linear/rules.js +6 -3
  66. package/dist/integrations/linear/rules.js.map +1 -1
  67. package/dist/integrations/pull-request-head.d.ts +18 -0
  68. package/dist/integrations/pull-request-head.d.ts.map +1 -0
  69. package/dist/integrations/pull-request-head.js +45 -0
  70. package/dist/integrations/pull-request-head.js.map +1 -0
  71. package/dist/routes/attention-providers.js +3 -3
  72. package/dist/routes/attention-providers.js.map +1 -1
  73. package/dist/routes/contracts.js +1 -1
  74. package/dist/routes/intake.d.ts +3 -0
  75. package/dist/routes/intake.d.ts.map +1 -1
  76. package/dist/routes/intake.js +47 -6
  77. package/dist/routes/intake.js.map +1 -1
  78. package/dist/routes/surface.d.ts.map +1 -1
  79. package/dist/routes/surface.js +3 -2
  80. package/dist/routes/surface.js.map +1 -1
  81. package/dist/routes/work-items.d.ts.map +1 -1
  82. package/dist/routes/work-items.js +3 -4
  83. package/dist/routes/work-items.js.map +1 -1
  84. package/dist/rules/decision-applicability.d.ts +6 -0
  85. package/dist/rules/decision-applicability.d.ts.map +1 -1
  86. package/dist/rules/decision-applicability.js +10 -1
  87. package/dist/rules/decision-applicability.js.map +1 -1
  88. package/dist/rules/dispatcher.d.ts.map +1 -1
  89. package/dist/rules/dispatcher.js +20 -15
  90. package/dist/rules/dispatcher.js.map +1 -1
  91. package/dist/rules/event-rules.d.ts +23 -0
  92. package/dist/rules/event-rules.d.ts.map +1 -0
  93. package/dist/rules/event-rules.js +46 -0
  94. package/dist/rules/event-rules.js.map +1 -0
  95. package/dist/rules/index.js +1 -1
  96. package/dist/rules/processor.js +1 -1
  97. package/dist/rules/review-source-tool.d.ts +5 -3
  98. package/dist/rules/review-source-tool.d.ts.map +1 -1
  99. package/dist/rules/review-source-tool.js.map +1 -1
  100. package/dist/rules/tools.js +1 -1
  101. package/dist/rules/transition-service.js +1 -1
  102. package/dist/rules/types.d.ts +47 -0
  103. package/dist/rules/types.d.ts.map +1 -1
  104. package/dist/rules/types.js.map +1 -1
  105. package/dist/storage/domains/comments/base.d.ts +3 -0
  106. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  107. package/dist/storage/domains/comments/base.js +8 -1
  108. package/dist/storage/domains/comments/base.js.map +1 -1
  109. package/dist/storage/domains/work-items/base.d.ts +7 -3
  110. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  111. package/dist/storage/domains/work-items/base.js +66 -16
  112. package/dist/storage/domains/work-items/base.js.map +1 -1
  113. package/dist/storage/domains/work-items/board-version.d.ts +26 -0
  114. package/dist/storage/domains/work-items/board-version.d.ts.map +1 -0
  115. package/dist/storage/domains/work-items/board-version.js +110 -0
  116. package/dist/storage/domains/work-items/board-version.js.map +1 -0
  117. package/dist/supervisor/health.d.ts.map +1 -1
  118. package/dist/supervisor/health.js +17 -5
  119. package/dist/supervisor/health.js.map +1 -1
  120. package/dist/supervisor/read-tools.d.ts.map +1 -1
  121. package/dist/supervisor/read-tools.js +13 -8
  122. package/dist/supervisor/read-tools.js.map +1 -1
  123. package/dist/supervisor/write-tools.d.ts.map +1 -1
  124. package/dist/supervisor/write-tools.js +5 -1
  125. package/dist/supervisor/write-tools.js.map +1 -1
  126. package/dist/workspace.d.ts.map +1 -1
  127. package/dist/workspace.js +17 -1
  128. package/dist/workspace.js.map +1 -1
  129. package/factory-skills/factory-rereview/SKILL.md +4 -4
  130. package/factory-skills/factory-review/SKILL.md +2 -2
  131. package/package.json +6 -6
@@ -0,0 +1,26 @@
1
+ import type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';
2
+ /**
3
+ * One row per project whose `version` is replaced after every committed write to that project's
4
+ * `work_items`. Readers of the full card list compare it against the version their snapshot was
5
+ * taken at, so an unchanged board costs one indexed row read instead of every card.
6
+ */
7
+ export declare const FACTORY_BOARD_VERSIONS_SCHEMA: CollectionSchema;
8
+ /** A project, or every project in an org when a write's filter only names the org. */
9
+ export interface BoardScope {
10
+ orgId: string;
11
+ factoryProjectId?: string;
12
+ }
13
+ /**
14
+ * Wrap `ops` so every write to `work_items` reports the board it changed, once the write has
15
+ * resolved. Writes outside any tenant (`dangerouslyClearAll`) report nothing.
16
+ */
17
+ export declare function trackBoardWrites(ops: FactoryStorageOps, changed: (scope: BoardScope) => void | Promise<void>): FactoryStorageOps;
18
+ /** Collects the boards a transaction changed so they can be bumped once it has committed. */
19
+ export declare class BoardScopes {
20
+ #private;
21
+ add: (scope: BoardScope) => void;
22
+ values(): BoardScope[];
23
+ }
24
+ export declare function bumpBoardVersions(ops: FactoryStorageOps, scopes: readonly BoardScope[]): Promise<void>;
25
+ export declare function readBoardVersion(ops: FactoryStorageOps, orgId: string, factoryProjectId: string): Promise<string | undefined>;
26
+ //# sourceMappingURL=board-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-version.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/work-items/board-version.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAmB,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEjG;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,gBAU3C,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACnD,iBAAiB,CA6CnB;AAED,6FAA6F;AAC7F,qBAAa,WAAW;;IAGtB,GAAG,GAAI,OAAO,UAAU,KAAG,IAAI,CAE7B;IAEF,MAAM,IAAI,UAAU,EAAE;CAGvB;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5G;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B"}
@@ -0,0 +1,110 @@
1
+ import { randomUUID } from "crypto";
2
+ //#region src/storage/domains/work-items/board-version.ts
3
+ /**
4
+ * One row per project whose `version` is replaced after every committed write to that project's
5
+ * `work_items`. Readers of the full card list compare it against the version their snapshot was
6
+ * taken at, so an unchanged board costs one indexed row read instead of every card.
7
+ */
8
+ const FACTORY_BOARD_VERSIONS_SCHEMA = {
9
+ name: "factory_board_versions",
10
+ columns: {
11
+ id: { type: "uuid-pk" },
12
+ org_id: { type: "text" },
13
+ factory_project_id: { type: "text" },
14
+ version: { type: "text" },
15
+ updated_at: { type: "timestamp" }
16
+ },
17
+ uniqueIndexes: [{
18
+ name: "factory_board_versions_scope_unique",
19
+ columns: ["org_id", "factory_project_id"]
20
+ }]
21
+ };
22
+ const scopeKey = (scope) => `${scope.orgId}\u0000${scope.factoryProjectId ?? ""}`;
23
+ function scopeOfRow(row) {
24
+ if (!row || typeof row.org_id !== "string") return void 0;
25
+ return typeof row.factory_project_id === "string" ? {
26
+ orgId: row.org_id,
27
+ factoryProjectId: row.factory_project_id
28
+ } : { orgId: row.org_id };
29
+ }
30
+ function scopeOfWhere(where) {
31
+ if (typeof where.org_id !== "string") return void 0;
32
+ return typeof where.factory_project_id === "string" ? {
33
+ orgId: where.org_id,
34
+ factoryProjectId: where.factory_project_id
35
+ } : { orgId: where.org_id };
36
+ }
37
+ /**
38
+ * Wrap `ops` so every write to `work_items` reports the board it changed, once the write has
39
+ * resolved. Writes outside any tenant (`dangerouslyClearAll`) report nothing.
40
+ */
41
+ function trackBoardWrites(ops, changed) {
42
+ const report = async (scope) => {
43
+ if (scope) await changed(scope);
44
+ };
45
+ return new Proxy(ops, { get(target, property, receiver) {
46
+ const value = Reflect.get(target, property, receiver);
47
+ if (typeof value !== "function") return value;
48
+ switch (property) {
49
+ case "updateAtomic": return async (collection, where, updater, ...args) => {
50
+ let patched = false;
51
+ const row = await value.call(target, collection, where, async (current) => {
52
+ const patch = await updater(current);
53
+ patched = patch != null;
54
+ return patch;
55
+ }, ...args);
56
+ if (collection === "work_items" && patched) await report(scopeOfRow(row));
57
+ return row;
58
+ };
59
+ case "insertOne":
60
+ case "upsertOne": return async (collection, ...args) => {
61
+ const row = await value.call(target, collection, ...args);
62
+ if (collection === "work_items") await report(scopeOfRow(row));
63
+ return row;
64
+ };
65
+ case "updateMany":
66
+ case "deleteMany": return async (collection, where, ...args) => {
67
+ const count = await value.call(target, collection, where, ...args);
68
+ if (collection === "work_items" && count > 0) await report(scopeOfWhere(where));
69
+ return count;
70
+ };
71
+ default: return value.bind(target);
72
+ }
73
+ } });
74
+ }
75
+ /** Collects the boards a transaction changed so they can be bumped once it has committed. */
76
+ var BoardScopes = class {
77
+ #scopes = /* @__PURE__ */ new Map();
78
+ add = (scope) => {
79
+ this.#scopes.set(scopeKey(scope), scope);
80
+ };
81
+ values() {
82
+ return [...this.#scopes.values()];
83
+ }
84
+ };
85
+ async function bumpBoardVersions(ops, scopes) {
86
+ for (const scope of scopes) {
87
+ const version = randomUUID();
88
+ const updated_at = /* @__PURE__ */ new Date();
89
+ if (scope.factoryProjectId) await ops.upsertOne(FACTORY_BOARD_VERSIONS_SCHEMA.name, ["org_id", "factory_project_id"], {
90
+ org_id: scope.orgId,
91
+ factory_project_id: scope.factoryProjectId,
92
+ version,
93
+ updated_at
94
+ });
95
+ else await ops.updateMany(FACTORY_BOARD_VERSIONS_SCHEMA.name, { org_id: scope.orgId }, {
96
+ version,
97
+ updated_at
98
+ });
99
+ }
100
+ }
101
+ async function readBoardVersion(ops, orgId, factoryProjectId) {
102
+ return (await ops.findOne(FACTORY_BOARD_VERSIONS_SCHEMA.name, {
103
+ org_id: orgId,
104
+ factory_project_id: factoryProjectId
105
+ }))?.version;
106
+ }
107
+ //#endregion
108
+ export { BoardScopes, FACTORY_BOARD_VERSIONS_SCHEMA, bumpBoardVersions, readBoardVersion, trackBoardWrites };
109
+
110
+ //# sourceMappingURL=board-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-version.js","names":["#scopes"],"sources":["../../../../src/storage/domains/work-items/board-version.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/**\n * One row per project whose `version` is replaced after every committed write to that project's\n * `work_items`. Readers of the full card list compare it against the version their snapshot was\n * taken at, so an unchanged board costs one indexed row read instead of every card.\n */\nexport const FACTORY_BOARD_VERSIONS_SCHEMA: CollectionSchema = {\n name: 'factory_board_versions',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n version: { type: 'text' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'factory_board_versions_scope_unique', columns: ['org_id', 'factory_project_id'] }],\n};\n\n/** A project, or every project in an org when a write's filter only names the org. */\nexport interface BoardScope {\n orgId: string;\n factoryProjectId?: string;\n}\n\nconst scopeKey = (scope: BoardScope) => `${scope.orgId}\\u0000${scope.factoryProjectId ?? ''}`;\n\nfunction scopeOfRow(row: Record<string, unknown> | null): BoardScope | undefined {\n if (!row || typeof row.org_id !== 'string') return undefined;\n return typeof row.factory_project_id === 'string'\n ? { orgId: row.org_id, factoryProjectId: row.factory_project_id }\n : { orgId: row.org_id };\n}\n\nfunction scopeOfWhere(where: CollectionWhere): BoardScope | undefined {\n if (typeof where.org_id !== 'string') return undefined;\n return typeof where.factory_project_id === 'string'\n ? { orgId: where.org_id, factoryProjectId: where.factory_project_id }\n : { orgId: where.org_id };\n}\n\n/**\n * Wrap `ops` so every write to `work_items` reports the board it changed, once the write has\n * resolved. Writes outside any tenant (`dangerouslyClearAll`) report nothing.\n */\nexport function trackBoardWrites(\n ops: FactoryStorageOps,\n changed: (scope: BoardScope) => void | Promise<void>,\n): FactoryStorageOps {\n const report = async (scope: BoardScope | undefined) => {\n if (scope) await changed(scope);\n };\n return new Proxy(ops, {\n get(target, property, receiver) {\n const value = Reflect.get(target, property, receiver);\n if (typeof value !== 'function') return value;\n switch (property) {\n case 'updateAtomic':\n return async (collection: string, where: unknown, updater: (row: unknown) => unknown, ...args: unknown[]) => {\n let patched = false;\n const row = await value.call(\n target,\n collection,\n where,\n async (current: unknown) => {\n const patch = await updater(current);\n patched = patch != null;\n return patch;\n },\n ...args,\n );\n if (collection === 'work_items' && patched) await report(scopeOfRow(row));\n return row;\n };\n case 'insertOne':\n case 'upsertOne':\n return async (collection: string, ...args: unknown[]) => {\n const row = await value.call(target, collection, ...args);\n if (collection === 'work_items') await report(scopeOfRow(row));\n return row;\n };\n case 'updateMany':\n case 'deleteMany':\n return async (collection: string, where: CollectionWhere, ...args: unknown[]) => {\n const count: number = await value.call(target, collection, where, ...args);\n if (collection === 'work_items' && count > 0) await report(scopeOfWhere(where));\n return count;\n };\n default:\n return value.bind(target);\n }\n },\n });\n}\n\n/** Collects the boards a transaction changed so they can be bumped once it has committed. */\nexport class BoardScopes {\n readonly #scopes = new Map<string, BoardScope>();\n\n add = (scope: BoardScope): void => {\n this.#scopes.set(scopeKey(scope), scope);\n };\n\n values(): BoardScope[] {\n return [...this.#scopes.values()];\n }\n}\n\nexport async function bumpBoardVersions(ops: FactoryStorageOps, scopes: readonly BoardScope[]): Promise<void> {\n for (const scope of scopes) {\n const version = randomUUID();\n const updated_at = new Date();\n if (scope.factoryProjectId) {\n await ops.upsertOne(FACTORY_BOARD_VERSIONS_SCHEMA.name, ['org_id', 'factory_project_id'], {\n org_id: scope.orgId,\n factory_project_id: scope.factoryProjectId,\n version,\n updated_at,\n });\n } else {\n // Projects without a row yet have no version, which never matches a snapshot.\n await ops.updateMany(FACTORY_BOARD_VERSIONS_SCHEMA.name, { org_id: scope.orgId }, { version, updated_at });\n }\n }\n}\n\nexport async function readBoardVersion(\n ops: FactoryStorageOps,\n orgId: string,\n factoryProjectId: string,\n): Promise<string | undefined> {\n const row = await ops.findOne<{ version: string }>(FACTORY_BOARD_VERSIONS_SCHEMA.name, {\n org_id: orgId,\n factory_project_id: factoryProjectId,\n });\n return row?.version;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,KAAK,EACV,6BAA6B,EAC7B,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,eAAe,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAKvC,CAAC;AAoDF,UAAU,YAAY;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,6BAA6B,EAAE,CAAC;IAC3C,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,cAAc,GAAG,SAAS,CAAC;CACnE;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,IAAI,EACT,UAAU,GAAE,uBAAmD,GAC9D,mBAAmB,CA0IrB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,EAClD,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,uBAAuB,CAAA;CAAO,GACjE,OAAO,CAAC,mBAAmB,CAAC,CAY9B"}
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,KAAK,EACV,6BAA6B,EAC7B,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAChC,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,eAAe,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAKvC,CAAC;AAoDF,UAAU,YAAY;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,SAAS,EAAE,6BAA6B,EAAE,CAAC;IAC3C,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IACpC,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,cAAc,GAAG,SAAS,CAAC;CACnE;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,IAAI,EACT,UAAU,GAAE,uBAAmD,GAC9D,mBAAmB,CAwJrB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,EAClD,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,uBAAuB,CAAA;CAAO,GACjE,OAAO,CAAC,mBAAmB,CAAC,CAc9B"}
@@ -1,5 +1,5 @@
1
- import { boardForWorkItem, workItemPhaseSemantics } from "../boards/semantics.js";
2
1
  import { NEEDS_APPROVAL_LABEL, factoryRuleStage } from "../rules/types.js";
2
+ import { boardForWorkItem, workItemPhaseSemantics } from "../boards/semantics.js";
3
3
  import "../boards/index.js";
4
4
  //#region src/supervisor/health.ts
5
5
  /**
@@ -139,7 +139,18 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
139
139
  seats.push(binding);
140
140
  activeSeatsByItem.set(item.id, seats);
141
141
  }
142
- const inFlightByItem = new Set(inputs.decisions.filter((d) => d.workItemId && IN_FLIGHT_DECISION_STATUSES.has(d.status)).map((d) => d.workItemId));
142
+ const inFlightByItem = /* @__PURE__ */ new Map();
143
+ for (const decision of inputs.decisions) {
144
+ if (!decision.workItemId || !IN_FLIGHT_DECISION_STATUSES.has(decision.status)) continue;
145
+ const inFlight = inFlightByItem.get(decision.workItemId) ?? [];
146
+ inFlight.push(decision);
147
+ inFlightByItem.set(decision.workItemId, inFlight);
148
+ }
149
+ const seatedFor = (itemId, role) => (activeSeatsByItem.get(itemId) ?? []).some((binding) => !role || binding.role === role);
150
+ const movingFor = (itemId, role) => (inFlightByItem.get(itemId) ?? []).some((decision) => {
151
+ const decided = decisionRole(decision);
152
+ return !role || decided === null || decided === role;
153
+ });
143
154
  for (const item of inputs.items) {
144
155
  const stage = factoryRuleStage(item.stages);
145
156
  const semantics = stage ? inputs.phaseSemantics(item) : void 0;
@@ -159,13 +170,14 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
159
170
  workItemId: item.id
160
171
  }
161
172
  });
162
- } else if (!activeSeatsByItem.has(item.id) && !inFlightByItem.has(item.id)) {
173
+ } else if (!seatedFor(item.id, semantics.role) && !movingFor(item.id, semantics.role)) {
163
174
  const role = semantics.role;
175
+ const roleLabel = role ? `${role} ` : "";
164
176
  findings.push({
165
177
  kind: "seat-missing",
166
178
  id: `seat-missing:${item.id}`,
167
179
  ...base,
168
- evidence: `In ${stage} since ${enteredAt?.toISOString() ?? "unknown"} with no active seat and no decision in flight; nothing will move it.`,
180
+ evidence: `In ${stage} since ${enteredAt?.toISOString() ?? "unknown"} with no active ${roleLabel}seat and no ${roleLabel}decision in flight; nothing will move it.`,
169
181
  beganAt: enteredAt?.toISOString() ?? null,
170
182
  suggestedRepair: role ? {
171
183
  action: "start-run",
@@ -198,7 +210,7 @@ function computeFactoryHealth(inputs, now, thresholds = DEFAULT_HEALTH_THRESHOLD
198
210
  async function runFactoryHealthCheck(workItems, boards, scope, options = {}) {
199
211
  const [items, decisions, bindings, pendingStarts] = await Promise.all([
200
212
  workItems.list(scope),
201
- workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),
213
+ workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { statuses: [...IN_FLIGHT_DECISION_STATUSES] }),
202
214
  workItems.listRunBindings(scope.orgId, scope.factoryProjectId),
203
215
  workItems.listPendingStarts(scope.orgId, scope.factoryProjectId)
204
216
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"health.js","names":[],"sources":["../../src/supervisor/health.ts"],"sourcesContent":["/**\n * Deterministic Factory health check.\n *\n * Every finding is computed from storage rows alone — no model, no controller —\n * so the supervisor agent explains findings rather than inventing them, and the\n * same list can be rendered by the UI or polled by a timer. Each finding names\n * the row it came from and the standard repair a person (or the supervisor's\n * write tools) would apply.\n */\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry, PhaseSemantics } from '../boards/index.js';\nimport { factoryRuleStage, NEEDS_APPROVAL_LABEL } from '../rules/types.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryHealthFindingKind =\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'revoke-binding'; bindingId: string }\n | { action: 'accept-work-item'; workItemId: string }\n | { action: 'start-run'; workItemId: string; role: string }\n | { action: 'reconcile-labels'; workItemId: string };\n\nexport interface FactoryHealthFinding {\n kind: FactoryHealthFindingKind;\n /** Stable per (kind, subject) so the UI and attention inbox can dedupe. */\n id: string;\n workItemId: string | null;\n /** Card number as shown on the board, when the row carries one. */\n workItemNumber: number | null;\n title: string;\n /** One sentence of grounded evidence: ids, timestamps, error text. */\n evidence: string;\n /** ISO instant the condition began, when it is age-based. */\n beganAt: string | null;\n suggestedRepair: FactoryHealthRepair | null;\n}\n\nexport interface FactoryHealthReport {\n checkedAt: string;\n findings: FactoryHealthFinding[];\n counts: Record<FactoryHealthFindingKind, number>;\n}\n\nexport interface FactoryHealthThresholds {\n /** A retry/pending decision whose `availableAt` is this far in the past never got picked up. */\n stuckDecisionMs: number;\n /** A lease this far past its expiry belongs to a worker that died mid-dispatch. */\n expiredLeaseMs: number;\n /** A pending start this old is not \"booting\", it is stalled. */\n stalledStartMs: number;\n /** Held cards older than this are worth nudging a maintainer about. */\n waitingOnPersonMs: number;\n}\n\nexport const DEFAULT_HEALTH_THRESHOLDS: FactoryHealthThresholds = {\n stuckDecisionMs: 10 * 60_000,\n expiredLeaseMs: 5 * 60_000,\n stalledStartMs: 10 * 60_000,\n waitingOnPersonMs: 24 * 60 * 60_000,\n};\n\nconst FINDING_KINDS: FactoryHealthFindingKind[] = [\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\nfunction itemNumber(item: WorkItemRow | undefined): number | null {\n const number = item?.metadata?.number ?? item?.metadata?.githubIssueNumber ?? item?.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction decisionType(decision: FactoryDeferredDecisionRecord): string {\n const type = decision.decision.type;\n return typeof type === 'string' ? type : 'decision';\n}\n\nfunction decisionRole(decision: FactoryDeferredDecisionRecord): string | null {\n const role = decision.decision.role;\n return typeof role === 'string' ? role : null;\n}\n\nfunction describeDecision(decision: FactoryDeferredDecisionRecord): string {\n const role = decisionRole(decision);\n return role ? `${decisionType(decision)} (${role})` : decisionType(decision);\n}\n\nfunction truncate(text: string, max = 200): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction stageEnteredAt(item: WorkItemRow): Date | null {\n const open = [...item.stageHistory].reverse().find(entry => !entry.exitedAt);\n const at = open ? Date.parse(open.enteredAt) : Number.NaN;\n return Number.isFinite(at) ? new Date(at) : null;\n}\n\nfunction beganAtMs(finding: FactoryHealthFinding): number {\n return finding.beganAt === null ? Number.MAX_SAFE_INTEGER : Date.parse(finding.beganAt);\n}\n\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\n /**\n * What the item's installed board says its current phase means. Undefined when the\n * board is not installed, the phase is undeclared, or the row spans several stages;\n * the check then neither revokes nor starts anything on a guess.\n */\n phaseSemantics: (item: WorkItemRow) => PhaseSemantics | undefined;\n}\n\n/** Pure: findings from rows. Exported so tests can feed fixtures directly. */\nexport function computeFactoryHealth(\n inputs: HealthInputs,\n now: Date,\n thresholds: FactoryHealthThresholds = DEFAULT_HEALTH_THRESHOLDS,\n): FactoryHealthReport {\n const itemsById = new Map(inputs.items.map(item => [item.id, item]));\n const findings: FactoryHealthFinding[] = [];\n const subject = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return { workItemId, workItemNumber: itemNumber(item), title: item?.title ?? '' };\n };\n\n for (const decision of inputs.decisions) {\n const base = subject(decision.workItemId);\n if (decision.status === 'retry' || decision.status === 'pending') {\n const overdue = now.getTime() - decision.availableAt.getTime();\n if (overdue > thresholds.stuckDecisionMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) has been ${decision.status} since ${decision.availableAt.toISOString()} with ${decision.attempts} attempt(s) and was never leased; is the dispatcher running?`,\n beganAt: decision.availableAt.toISOString(),\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'leased' && decision.leaseExpiresAt) {\n const expired = now.getTime() - decision.leaseExpiresAt.getTime();\n if (expired > thresholds.expiredLeaseMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) is still leased by ${decision.leaseOwner ?? 'unknown'} though the lease expired at ${decision.leaseExpiresAt.toISOString()}; the worker likely died mid-dispatch.`,\n beganAt: decision.leaseExpiresAt.toISOString(),\n suggestedRepair: null,\n });\n }\n }\n }\n\n const bindingsById = new Map(inputs.bindings.map(binding => [binding.id, binding]));\n for (const start of inputs.pendingStarts) {\n if (start.status === 'sent') continue;\n const binding = bindingsById.get(start.bindingId);\n const base = subject(binding?.workItemId ?? null);\n const age = now.getTime() - start.createdAt.getTime();\n if (start.status === 'failed' || age > thresholds.stalledStartMs) {\n findings.push({\n kind: 'start-stalled',\n id: `start-stalled:${start.id}`,\n ...base,\n evidence: `Pending start ${start.id}${binding ? ` for the ${binding.role} seat` : ''} is ${start.status} since ${start.createdAt.toISOString()} after ${start.attempts} attempt(s)${start.lastError ? `: ${truncate(start.lastError)}` : '.'}`,\n beganAt: start.createdAt.toISOString(),\n suggestedRepair: binding ? { action: 'revoke-binding', bindingId: binding.id } : null,\n });\n }\n }\n\n const activeSeatsByItem = new Map<string, FactoryRunBindingRecord[]>();\n for (const binding of inputs.bindings) {\n if (binding.status !== 'active') continue;\n const item = itemsById.get(binding.workItemId);\n const stage = item ? factoryRuleStage(item.stages) : undefined;\n if (!item || inputs.phaseSemantics(item)?.kind === 'terminal') {\n findings.push({\n kind: 'seat-orphaned',\n id: `seat-orphaned:${binding.id}`,\n ...subject(item ? item.id : null),\n evidence: item\n ? `Binding ${binding.id} (${binding.role}) is still active though the card is in ${stage ?? item.stages.join('+')}.`\n : `Binding ${binding.id} (${binding.role}) is active for work item ${binding.workItemId}, which no longer exists.`,\n beganAt: item ? (stageEnteredAt(item)?.toISOString() ?? null) : null,\n suggestedRepair: { action: 'revoke-binding', bindingId: binding.id },\n });\n continue;\n }\n const seats = activeSeatsByItem.get(item.id) ?? [];\n seats.push(binding);\n activeSeatsByItem.set(item.id, seats);\n }\n\n const inFlightByItem = new Set(\n inputs.decisions.filter(d => d.workItemId && IN_FLIGHT_DECISION_STATUSES.has(d.status)).map(d => d.workItemId!),\n );\n for (const item of inputs.items) {\n const stage = factoryRuleStage(item.stages);\n const semantics = stage ? inputs.phaseSemantics(item) : undefined;\n if (!stage || semantics?.kind !== 'working') continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n // Work's triage seat holds a classified non-bug until a maintainer accepts it. Only\n // Work has that gate, so this is keyed on the board, not on the phase name.\n const held =\n boardForWorkItem(item) === 'work' &&\n stage === 'triage' &&\n item.triageType !== null &&\n item.triageType !== 'bug' &&\n !item.acceptedAt;\n if (held) {\n if (inStageMs !== null && inStageMs > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'held-waiting',\n id: `held-waiting:${item.id}`,\n ...base,\n evidence: `Triaged as \"${item.triageType}\" and waiting for a maintainer's decision since ${enteredAt!.toISOString()}.`,\n beganAt: enteredAt!.toISOString(),\n suggestedRepair: { action: 'accept-work-item', workItemId: item.id },\n });\n }\n } else if (!activeSeatsByItem.has(item.id) && !inFlightByItem.has(item.id)) {\n const role = semantics.role;\n findings.push({\n kind: 'seat-missing',\n id: `seat-missing:${item.id}`,\n ...base,\n evidence: `In ${stage} since ${enteredAt?.toISOString() ?? 'unknown'} with no active seat and no decision in flight; nothing will move it.`,\n beganAt: enteredAt?.toISOString() ?? null,\n suggestedRepair: role ? { action: 'start-run', workItemId: item.id, role } : null,\n });\n }\n\n if (item.acceptedAt && itemLabels(item).includes(NEEDS_APPROVAL_LABEL)) {\n findings.push({\n kind: 'label-drift',\n id: `label-drift:${item.id}`,\n ...base,\n evidence: `Accepted at ${item.acceptedAt.toISOString()} but the last observed labels still include \"${NEEDS_APPROVAL_LABEL}\".`,\n beganAt: item.acceptedAt.toISOString(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => beganAtMs(a) - beganAtMs(b) || a.id.localeCompare(b.id));\n const counts = Object.fromEntries(FINDING_KINDS.map(kind => [kind, 0])) as Record<FactoryHealthFindingKind, number>;\n for (const finding of findings) counts[finding.kind] += 1;\n return { checkedAt: now.toISOString(), findings, counts };\n}\n\nexport async function runFactoryHealthCheck(\n workItems: WorkItemsStorage,\n boards: BoardRegistry,\n scope: { orgId: string; factoryProjectId: string },\n options: { now?: Date; thresholds?: FactoryHealthThresholds } = {},\n): Promise<FactoryHealthReport> {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n workItems.list(scope),\n workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n return computeFactoryHealth(\n { items, decisions, bindings, pendingStarts, phaseSemantics: item => workItemPhaseSemantics(boards, item) },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"health.js","names":[],"sources":["../../src/supervisor/health.ts"],"sourcesContent":["/**\n * Deterministic Factory health check.\n *\n * Every finding is computed from storage rows alone — no model, no controller —\n * so the supervisor agent explains findings rather than inventing them, and the\n * same list can be rendered by the UI or polled by a timer. Each finding names\n * the row it came from and the standard repair a person (or the supervisor's\n * write tools) would apply.\n */\n\nimport { boardForWorkItem, workItemPhaseSemantics } from '../boards/index.js';\nimport type { BoardRegistry, PhaseSemantics } from '../boards/index.js';\nimport { factoryRuleStage, NEEDS_APPROVAL_LABEL } from '../rules/types.js';\nimport type {\n FactoryDeferredDecisionRecord,\n FactoryPendingStartRecord,\n FactoryRunBindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryHealthFindingKind =\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'revoke-binding'; bindingId: string }\n | { action: 'accept-work-item'; workItemId: string }\n | { action: 'start-run'; workItemId: string; role: string }\n | { action: 'reconcile-labels'; workItemId: string };\n\nexport interface FactoryHealthFinding {\n kind: FactoryHealthFindingKind;\n /** Stable per (kind, subject) so the UI and attention inbox can dedupe. */\n id: string;\n workItemId: string | null;\n /** Card number as shown on the board, when the row carries one. */\n workItemNumber: number | null;\n title: string;\n /** One sentence of grounded evidence: ids, timestamps, error text. */\n evidence: string;\n /** ISO instant the condition began, when it is age-based. */\n beganAt: string | null;\n suggestedRepair: FactoryHealthRepair | null;\n}\n\nexport interface FactoryHealthReport {\n checkedAt: string;\n findings: FactoryHealthFinding[];\n counts: Record<FactoryHealthFindingKind, number>;\n}\n\nexport interface FactoryHealthThresholds {\n /** A retry/pending decision whose `availableAt` is this far in the past never got picked up. */\n stuckDecisionMs: number;\n /** A lease this far past its expiry belongs to a worker that died mid-dispatch. */\n expiredLeaseMs: number;\n /** A pending start this old is not \"booting\", it is stalled. */\n stalledStartMs: number;\n /** Held cards older than this are worth nudging a maintainer about. */\n waitingOnPersonMs: number;\n}\n\nexport const DEFAULT_HEALTH_THRESHOLDS: FactoryHealthThresholds = {\n stuckDecisionMs: 10 * 60_000,\n expiredLeaseMs: 5 * 60_000,\n stalledStartMs: 10 * 60_000,\n waitingOnPersonMs: 24 * 60 * 60_000,\n};\n\nconst FINDING_KINDS: FactoryHealthFindingKind[] = [\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\nfunction itemNumber(item: WorkItemRow | undefined): number | null {\n const number = item?.metadata?.number ?? item?.metadata?.githubIssueNumber ?? item?.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction decisionType(decision: FactoryDeferredDecisionRecord): string {\n const type = decision.decision.type;\n return typeof type === 'string' ? type : 'decision';\n}\n\nfunction decisionRole(decision: FactoryDeferredDecisionRecord): string | null {\n const role = decision.decision.role;\n return typeof role === 'string' ? role : null;\n}\n\nfunction describeDecision(decision: FactoryDeferredDecisionRecord): string {\n const role = decisionRole(decision);\n return role ? `${decisionType(decision)} (${role})` : decisionType(decision);\n}\n\nfunction truncate(text: string, max = 200): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction stageEnteredAt(item: WorkItemRow): Date | null {\n const open = [...item.stageHistory].reverse().find(entry => !entry.exitedAt);\n const at = open ? Date.parse(open.enteredAt) : Number.NaN;\n return Number.isFinite(at) ? new Date(at) : null;\n}\n\nfunction beganAtMs(finding: FactoryHealthFinding): number {\n return finding.beganAt === null ? Number.MAX_SAFE_INTEGER : Date.parse(finding.beganAt);\n}\n\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\n /**\n * What the item's installed board says its current phase means. Undefined when the\n * board is not installed, the phase is undeclared, or the row spans several stages;\n * the check then neither revokes nor starts anything on a guess.\n */\n phaseSemantics: (item: WorkItemRow) => PhaseSemantics | undefined;\n}\n\n/** Pure: findings from rows. Exported so tests can feed fixtures directly. */\nexport function computeFactoryHealth(\n inputs: HealthInputs,\n now: Date,\n thresholds: FactoryHealthThresholds = DEFAULT_HEALTH_THRESHOLDS,\n): FactoryHealthReport {\n const itemsById = new Map(inputs.items.map(item => [item.id, item]));\n const findings: FactoryHealthFinding[] = [];\n const subject = (workItemId: string | null) => {\n const item = workItemId ? itemsById.get(workItemId) : undefined;\n return { workItemId, workItemNumber: itemNumber(item), title: item?.title ?? '' };\n };\n\n for (const decision of inputs.decisions) {\n const base = subject(decision.workItemId);\n if (decision.status === 'retry' || decision.status === 'pending') {\n const overdue = now.getTime() - decision.availableAt.getTime();\n if (overdue > thresholds.stuckDecisionMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) has been ${decision.status} since ${decision.availableAt.toISOString()} with ${decision.attempts} attempt(s) and was never leased; is the dispatcher running?`,\n beganAt: decision.availableAt.toISOString(),\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'leased' && decision.leaseExpiresAt) {\n const expired = now.getTime() - decision.leaseExpiresAt.getTime();\n if (expired > thresholds.expiredLeaseMs) {\n findings.push({\n kind: 'decision-stuck',\n id: `decision-stuck:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) is still leased by ${decision.leaseOwner ?? 'unknown'} though the lease expired at ${decision.leaseExpiresAt.toISOString()}; the worker likely died mid-dispatch.`,\n beganAt: decision.leaseExpiresAt.toISOString(),\n suggestedRepair: null,\n });\n }\n }\n }\n\n const bindingsById = new Map(inputs.bindings.map(binding => [binding.id, binding]));\n for (const start of inputs.pendingStarts) {\n if (start.status === 'sent') continue;\n const binding = bindingsById.get(start.bindingId);\n const base = subject(binding?.workItemId ?? null);\n const age = now.getTime() - start.createdAt.getTime();\n if (start.status === 'failed' || age > thresholds.stalledStartMs) {\n findings.push({\n kind: 'start-stalled',\n id: `start-stalled:${start.id}`,\n ...base,\n evidence: `Pending start ${start.id}${binding ? ` for the ${binding.role} seat` : ''} is ${start.status} since ${start.createdAt.toISOString()} after ${start.attempts} attempt(s)${start.lastError ? `: ${truncate(start.lastError)}` : '.'}`,\n beganAt: start.createdAt.toISOString(),\n suggestedRepair: binding ? { action: 'revoke-binding', bindingId: binding.id } : null,\n });\n }\n }\n\n const activeSeatsByItem = new Map<string, FactoryRunBindingRecord[]>();\n for (const binding of inputs.bindings) {\n if (binding.status !== 'active') continue;\n const item = itemsById.get(binding.workItemId);\n const stage = item ? factoryRuleStage(item.stages) : undefined;\n if (!item || inputs.phaseSemantics(item)?.kind === 'terminal') {\n findings.push({\n kind: 'seat-orphaned',\n id: `seat-orphaned:${binding.id}`,\n ...subject(item ? item.id : null),\n evidence: item\n ? `Binding ${binding.id} (${binding.role}) is still active though the card is in ${stage ?? item.stages.join('+')}.`\n : `Binding ${binding.id} (${binding.role}) is active for work item ${binding.workItemId}, which no longer exists.`,\n beganAt: item ? (stageEnteredAt(item)?.toISOString() ?? null) : null,\n suggestedRepair: { action: 'revoke-binding', bindingId: binding.id },\n });\n continue;\n }\n const seats = activeSeatsByItem.get(item.id) ?? [];\n seats.push(binding);\n activeSeatsByItem.set(item.id, seats);\n }\n\n const inFlightByItem = new Map<string, FactoryDeferredDecisionRecord[]>();\n for (const decision of inputs.decisions) {\n if (!decision.workItemId || !IN_FLIGHT_DECISION_STATUSES.has(decision.status)) continue;\n const inFlight = inFlightByItem.get(decision.workItemId) ?? [];\n inFlight.push(decision);\n inFlightByItem.set(decision.workItemId, inFlight);\n }\n // A seat or run left behind by an earlier phase cannot carry this one, so only the phase's\n // own role counts. Decisions without a role (a queued transition) still move the card.\n const seatedFor = (itemId: string, role: string | undefined) =>\n (activeSeatsByItem.get(itemId) ?? []).some(binding => !role || binding.role === role);\n const movingFor = (itemId: string, role: string | undefined) =>\n (inFlightByItem.get(itemId) ?? []).some(decision => {\n const decided = decisionRole(decision);\n return !role || decided === null || decided === role;\n });\n for (const item of inputs.items) {\n const stage = factoryRuleStage(item.stages);\n const semantics = stage ? inputs.phaseSemantics(item) : undefined;\n if (!stage || semantics?.kind !== 'working') continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n // Work's triage seat holds a classified non-bug until a maintainer accepts it. Only\n // Work has that gate, so this is keyed on the board, not on the phase name.\n const held =\n boardForWorkItem(item) === 'work' &&\n stage === 'triage' &&\n item.triageType !== null &&\n item.triageType !== 'bug' &&\n !item.acceptedAt;\n if (held) {\n if (inStageMs !== null && inStageMs > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'held-waiting',\n id: `held-waiting:${item.id}`,\n ...base,\n evidence: `Triaged as \"${item.triageType}\" and waiting for a maintainer's decision since ${enteredAt!.toISOString()}.`,\n beganAt: enteredAt!.toISOString(),\n suggestedRepair: { action: 'accept-work-item', workItemId: item.id },\n });\n }\n } else if (!seatedFor(item.id, semantics.role) && !movingFor(item.id, semantics.role)) {\n const role = semantics.role;\n const roleLabel = role ? `${role} ` : '';\n findings.push({\n kind: 'seat-missing',\n id: `seat-missing:${item.id}`,\n ...base,\n evidence: `In ${stage} since ${enteredAt?.toISOString() ?? 'unknown'} with no active ${roleLabel}seat and no ${roleLabel}decision in flight; nothing will move it.`,\n beganAt: enteredAt?.toISOString() ?? null,\n suggestedRepair: role ? { action: 'start-run', workItemId: item.id, role } : null,\n });\n }\n\n if (item.acceptedAt && itemLabels(item).includes(NEEDS_APPROVAL_LABEL)) {\n findings.push({\n kind: 'label-drift',\n id: `label-drift:${item.id}`,\n ...base,\n evidence: `Accepted at ${item.acceptedAt.toISOString()} but the last observed labels still include \"${NEEDS_APPROVAL_LABEL}\".`,\n beganAt: item.acceptedAt.toISOString(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => beganAtMs(a) - beganAtMs(b) || a.id.localeCompare(b.id));\n const counts = Object.fromEntries(FINDING_KINDS.map(kind => [kind, 0])) as Record<FactoryHealthFindingKind, number>;\n for (const finding of findings) counts[finding.kind] += 1;\n return { checkedAt: now.toISOString(), findings, counts };\n}\n\nexport async function runFactoryHealthCheck(\n workItems: WorkItemsStorage,\n boards: BoardRegistry,\n scope: { orgId: string; factoryProjectId: string },\n options: { now?: Date; thresholds?: FactoryHealthThresholds } = {},\n): Promise<FactoryHealthReport> {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n workItems.list(scope),\n workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, {\n statuses: [...IN_FLIGHT_DECISION_STATUSES],\n }),\n workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n return computeFactoryHealth(\n { items, decisions, bindings, pendingStarts, phaseSemantics: item => workItemPhaseSemantics(boards, item) },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/read-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,CAAC;KAC5D,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmID,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,0BAA0B,GAAG,gBAAgB,CAyPnG"}
1
+ {"version":3,"file":"read-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/read-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAuB,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE;YAAE,KAAK,EAAE,WAAW,CAAC;YAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;SAAE,CAAC;KAC5D,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmID,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,0BAA0B,GAAG,gBAAgB,CA8PnG"}
@@ -1,5 +1,6 @@
1
1
  import { FACTORY_RULE_STAGES, factoryRuleStage } from "../rules/types.js";
2
2
  import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
3
+ import { decisionRetryable, overtakenDecisionIds } from "../rules/decision-applicability.js";
3
4
  import { runFactoryHealthCheck } from "./health.js";
4
5
  import { createTool } from "@mastra/core/tools";
5
6
  import { z } from "zod";
@@ -39,7 +40,7 @@ function summarizeItem(item) {
39
40
  updatedAt: iso(item.updatedAt)
40
41
  };
41
42
  }
42
- function summarizeDecision(decision) {
43
+ function summarizeDecision(decision, overtaken = false) {
43
44
  const type = typeof decision.decision.type === "string" ? decision.decision.type : "decision";
44
45
  const role = typeof decision.decision.role === "string" ? decision.decision.role : null;
45
46
  const skill = typeof decision.decision.skillName === "string" ? decision.decision.skillName : null;
@@ -56,7 +57,7 @@ function summarizeDecision(decision) {
56
57
  leaseExpiresAt: iso(decision.leaseExpiresAt),
57
58
  failureCode: decision.failureCode,
58
59
  failureLabel: decision.failureCode ? factoryDispatchFailureMetadata(decision.failureCode).label : null,
59
- canRetry: decision.status === "failed" ? factoryDispatchFailureMetadata(decision.failureCode).canRetry : false,
60
+ canRetry: decisionRetryable(decision, overtaken),
60
61
  lastError: decision.lastError ? truncate(decision.lastError, MAX_ERROR) : null,
61
62
  approvedBy: decision.approvedBy,
62
63
  createdAt: iso(decision.createdAt),
@@ -134,6 +135,8 @@ function createFactorySupervisorReadTools(deps) {
134
135
  }
135
136
  const decisionsByStatus = {};
136
137
  for (const decision of decisions) decisionsByStatus[decision.status] = (decisionsByStatus[decision.status] ?? 0) + 1;
138
+ const failed = decisions.filter((d) => d.status === "failed").slice(0, MAX_LIST);
139
+ const overtaken = await overtakenDecisionIds(deps.workItems, deps.boards, scope, failed);
137
140
  const activeSeats = bindings.filter((binding) => binding.status === "active");
138
141
  const seatsByRole = {};
139
142
  for (const seat of activeSeats) seatsByRole[seat.role] = (seatsByRole[seat.role] ?? 0) + 1;
@@ -145,8 +148,8 @@ function createFactorySupervisorReadTools(deps) {
145
148
  heldForDecision: held
146
149
  },
147
150
  decisions: decisionsByStatus,
148
- openProposals: decisions.filter((d) => d.status === "proposed").map(summarizeDecision).slice(0, MAX_LIST),
149
- failedDecisions: decisions.filter((d) => d.status === "failed").map(summarizeDecision).slice(0, MAX_LIST),
151
+ openProposals: decisions.filter((d) => d.status === "proposed").map((decision) => summarizeDecision(decision)).slice(0, MAX_LIST),
152
+ failedDecisions: failed.map((decision) => summarizeDecision(decision, overtaken.has(decision.id))),
150
153
  activeSeats: {
151
154
  total: activeSeats.length,
152
155
  byRole: seatsByRole
@@ -172,7 +175,7 @@ function createFactorySupervisorReadTools(deps) {
172
175
  const item = await findItem(deps, ref);
173
176
  if (!item) throw new Error(`No work item matches ${ref.id ?? `#${ref.number}`} in this Factory.`);
174
177
  const [decisions, bindings, audit, feed, all] = await Promise.all([
175
- deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),
178
+ deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { workItemId: item.id }),
176
179
  deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id),
177
180
  deps.audit.list({
178
181
  orgId: scope.orgId,
@@ -188,6 +191,8 @@ function createFactorySupervisorReadTools(deps) {
188
191
  ]);
189
192
  const parent = item.parentWorkItemId ? all.find((candidate) => candidate.id === item.parentWorkItemId) : void 0;
190
193
  const children = all.filter((candidate) => candidate.parentWorkItemId === item.id);
194
+ const itemDecisions = decisions.filter((decision) => decision.workItemId === item.id).slice(-50);
195
+ const overtaken = await overtakenDecisionIds(deps.workItems, deps.boards, scope, itemDecisions.filter((decision) => decision.status === "failed"));
191
196
  return {
192
197
  item: summarizeItem(item),
193
198
  labels: itemLabels(item),
@@ -213,7 +218,7 @@ function createFactorySupervisorReadTools(deps) {
213
218
  createdAt: iso(binding.createdAt),
214
219
  revokedAt: iso(binding.revokedAt)
215
220
  })),
216
- decisions: decisions.filter((decision) => decision.workItemId === item.id).map(summarizeDecision).slice(-50),
221
+ decisions: itemDecisions.map((decision) => summarizeDecision(decision, overtaken.has(decision.id))),
217
222
  audit: audit.events.filter((event) => event.targets.some((target) => target.type === "work_item" && target.id === item.id)).slice(0, 25).map((event) => ({
218
223
  id: event.id,
219
224
  action: event.action,
@@ -239,7 +244,7 @@ function createFactorySupervisorReadTools(deps) {
239
244
  description: "Failed decisions grouped by failure code and error text, so a batch of cards that broke the same way reads as one incident. Also lists proposals waiting on a person and terminal cards that still hold seats.",
240
245
  inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_LIST).default(25) }).strict(),
241
246
  execute: async ({ limit }) => {
242
- const [decisions, items] = await Promise.all([deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId), deps.workItems.list(scope)]);
247
+ const [decisions, items] = await Promise.all([deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { statuses: ["failed", "proposed"] }), deps.workItems.list(scope)]);
243
248
  const itemsById = new Map(items.map((item) => [item.id, item]));
244
249
  const groups = /* @__PURE__ */ new Map();
245
250
  for (const decision of decisions) {
@@ -273,7 +278,7 @@ function createFactorySupervisorReadTools(deps) {
273
278
  };
274
279
  })
275
280
  })),
276
- proposals: decisions.filter((d) => d.status === "proposed").slice(0, limit).map(summarizeDecision)
281
+ proposals: decisions.filter((d) => d.status === "proposed").slice(0, limit).map((decision) => summarizeDecision(decision))
277
282
  };
278
283
  }
279
284
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"read-tools.js","names":[],"sources":["../../src/supervisor/read-tools.ts"],"sourcesContent":["/**\n * Supervisor read surface. Every tool is bounded (hard row caps, truncated\n * text) and answers with ids a person can click on the board, so the model\n * can explain a card's state without ever touching the database itself.\n */\n\nimport type { MastraDBMessage } from '@mastra/core/agent/message-list';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport { FACTORY_RULE_STAGES, factoryRuleStage } from '../rules/types.js';\nimport type { FactoryRuleStage } from '../rules/types.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryDeferredDecisionRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryHealthReport, FactoryHealthThresholds } from './health.js';\nimport { runFactoryHealthCheck } from './health.js';\n\nexport interface SupervisorScope {\n orgId: string;\n factoryProjectId: string;\n}\n\nexport interface SupervisorMessageReader {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n orderBy: { field: 'createdAt'; direction: 'ASC' | 'DESC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n}\n\nexport interface SupervisorReadDependencies {\n scope: SupervisorScope;\n workItems: WorkItemsStorage;\n boards: BoardRegistry;\n comments: WorkItemCommentsStorage;\n audit: AuditStorage;\n messageReader?: SupervisorMessageReader;\n healthThresholds?: FactoryHealthThresholds;\n now?: () => Date;\n}\n\nconst MAX_TEXT = 600;\nconst MAX_ERROR = 400;\nconst MAX_LIST = 50;\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction iso(value: Date | string | null | undefined): string | null {\n if (!value) return null;\n const date = value instanceof Date ? value : new Date(value);\n return Number.isFinite(date.getTime()) ? date.toISOString() : null;\n}\n\nfunction itemNumber(item: WorkItemRow): number | null {\n const number = item.metadata?.number ?? item.metadata?.githubIssueNumber ?? item.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction summarizeItem(item: WorkItemRow) {\n return {\n id: item.id,\n number: itemNumber(item),\n title: item.title,\n stage: factoryRuleStage(item.stages) ?? item.stages.join('+'),\n source: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : 'manual',\n url: typeof item.metadata?.url === 'string' ? item.metadata.url : null,\n triageType: item.triageType,\n acceptedAt: iso(item.acceptedAt),\n autonomyArmedAt: iso(item.autonomyArmedAt),\n parentWorkItemId: item.parentWorkItemId,\n revision: item.revision,\n updatedAt: iso(item.updatedAt),\n };\n}\n\nfunction summarizeDecision(decision: FactoryDeferredDecisionRecord) {\n const type = typeof decision.decision.type === 'string' ? decision.decision.type : 'decision';\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : null;\n const skill = typeof decision.decision.skillName === 'string' ? decision.decision.skillName : null;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n type,\n role,\n skill,\n status: decision.status,\n attempts: decision.attempts,\n availableAt: iso(decision.availableAt),\n leaseOwner: decision.leaseOwner,\n leaseExpiresAt: iso(decision.leaseExpiresAt),\n failureCode: decision.failureCode,\n failureLabel: decision.failureCode ? factoryDispatchFailureMetadata(decision.failureCode).label : null,\n canRetry: decision.status === 'failed' ? factoryDispatchFailureMetadata(decision.failureCode).canRetry : false,\n lastError: decision.lastError ? truncate(decision.lastError, MAX_ERROR) : null,\n approvedBy: decision.approvedBy,\n createdAt: iso(decision.createdAt),\n updatedAt: iso(decision.updatedAt),\n completedAt: iso(decision.completedAt),\n };\n}\n\nfunction messageParts(content: unknown): unknown[] {\n if (Array.isArray(content)) return content;\n if (content && typeof content === 'object') {\n const parts = (content as { parts?: unknown }).parts;\n if (Array.isArray(parts)) return parts;\n }\n return [];\n}\n\nfunction messageText(message: MastraDBMessage): string {\n const content: unknown = message.content;\n const parts = messageParts(content);\n const text: string[] = [];\n for (const part of parts) {\n if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'text') {\n const value = (part as { text?: unknown }).text;\n if (typeof value === 'string') text.push(value);\n }\n }\n if (text.length === 0 && typeof content === 'string') text.push(content);\n return text.join('\\n');\n}\n\nfunction messageToolCalls(message: MastraDBMessage): Array<{ tool: string; state: string | null }> {\n const parts = messageParts(message.content);\n const calls: Array<{ tool: string; state: string | null }> = [];\n for (const rawPart of parts) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : typeof part.type === 'string' && part.type.startsWith('tool-')\n ? part\n : undefined;\n if (!invocation) continue;\n const name = invocation.toolName ?? invocation.name ?? (typeof part.type === 'string' ? part.type.slice(5) : null);\n if (typeof name !== 'string') continue;\n calls.push({ tool: name, state: typeof invocation.state === 'string' ? invocation.state : null });\n }\n return calls;\n}\n\nasync function findItem(\n deps: SupervisorReadDependencies,\n ref: { id?: string; number?: number },\n): Promise<WorkItemRow | null> {\n if (ref.id) return deps.workItems.getForProject(deps.scope.orgId, deps.scope.factoryProjectId, ref.id);\n if (ref.number === undefined) return null;\n const items = await deps.workItems.list(deps.scope);\n const matches = items.filter(item => itemNumber(item) === ref.number);\n // Issue and PR numbers share a space on GitHub; prefer the Work-board card.\n return matches.find(item => item.externalSource?.type !== 'pull-request') ?? matches[0] ?? null;\n}\n\nconst itemRefSchema = z\n .object({\n id: z.string().uuid().optional().describe('Work item id.'),\n number: z.number().int().positive().optional().describe('Card number as shown on the board, e.g. 22874.'),\n })\n .refine(ref => ref.id !== undefined || ref.number !== undefined, { message: 'Provide an id or a number.' });\n\nexport function createFactorySupervisorReadTools(deps: SupervisorReadDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const scope = deps.scope;\n\n return {\n factory_overview: createTool({\n id: 'factory_overview',\n description:\n 'Counts per pipeline stage, decisions by status, active seats, open proposals and held cards for this Factory. Start here for \"what needs me\" questions.',\n inputSchema: z.object({}).strict(),\n execute: async () => {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n deps.workItems.list(scope),\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n deps.workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n const stages = Object.fromEntries(FACTORY_RULE_STAGES.map(stage => [stage, 0])) as Record<\n FactoryRuleStage,\n number\n >;\n let held = 0;\n for (const item of items) {\n const stage = factoryRuleStage(item.stages);\n if (stage) stages[stage] += 1;\n if (stage === 'triage' && item.triageType && item.triageType !== 'bug' && !item.acceptedAt) held += 1;\n }\n const decisionsByStatus: Record<string, number> = {};\n for (const decision of decisions) {\n decisionsByStatus[decision.status] = (decisionsByStatus[decision.status] ?? 0) + 1;\n }\n const activeSeats = bindings.filter(binding => binding.status === 'active');\n const seatsByRole: Record<string, number> = {};\n for (const seat of activeSeats) seatsByRole[seat.role] = (seatsByRole[seat.role] ?? 0) + 1;\n return {\n checkedAt: now().toISOString(),\n workItems: { total: items.length, byStage: stages, heldForDecision: held },\n decisions: decisionsByStatus,\n openProposals: decisions\n .filter(d => d.status === 'proposed')\n .map(summarizeDecision)\n .slice(0, MAX_LIST),\n failedDecisions: decisions\n .filter(d => d.status === 'failed')\n .map(summarizeDecision)\n .slice(0, MAX_LIST),\n activeSeats: { total: activeSeats.length, byRole: seatsByRole },\n pendingStarts: pendingStarts.filter(start => start.status !== 'sent').length,\n };\n },\n }),\n\n factory_health_check: createTool({\n id: 'factory_health_check',\n description:\n 'Deterministic list of things wrong with this Factory right now (stuck decisions, stalled starts, orphaned or missing seats, held cards waiting on a person, label drift). Each finding carries evidence and the standard repair. Explain these; do not invent findings that are not listed.',\n inputSchema: z.object({}).strict(),\n execute: async (): Promise<FactoryHealthReport> =>\n runFactoryHealthCheck(deps.workItems, deps.boards, scope, { now: now(), thresholds: deps.healthThresholds }),\n }),\n\n factory_inspect_work_item: createTool({\n id: 'factory_inspect_work_item',\n description:\n 'Everything the Factory knows about one card: row, stage history, seats (run bindings), decisions with errors, recent audit events, recent feed comments, linked parent/children and last observed labels. Use for \"why is #N in this state\".',\n inputSchema: itemRefSchema,\n execute: async ref => {\n const item = await findItem(deps, ref);\n if (!item) throw new Error(`No work item matches ${ref.id ?? `#${ref.number}`} in this Factory.`);\n const [decisions, bindings, audit, feed, all] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id),\n deps.audit.list({ orgId: scope.orgId, factoryProjectId: scope.factoryProjectId, limit: 200 }),\n deps.comments.listRecent({ ...scope, workItemId: item.id, limit: 10 }),\n deps.workItems.list(scope),\n ]);\n const parent = item.parentWorkItemId\n ? all.find(candidate => candidate.id === item.parentWorkItemId)\n : undefined;\n const children = all.filter(candidate => candidate.parentWorkItemId === item.id);\n return {\n item: summarizeItem(item),\n labels: itemLabels(item),\n stageHistory: item.stageHistory.map(entry => ({\n stage: entry.stage,\n enteredAt: entry.enteredAt,\n exitedAt: entry.exitedAt ?? null,\n by: entry.by,\n })),\n sessions: Object.entries(item.sessions).map(([role, ref]) => ({\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n branch: ref.branch,\n startedBy: ref.startedBy,\n })),\n seats: bindings.map(binding => ({\n id: binding.id,\n role: binding.role,\n status: binding.status,\n sessionId: binding.sessionId,\n threadId: binding.threadId,\n createdAt: iso(binding.createdAt),\n revokedAt: iso(binding.revokedAt),\n })),\n decisions: decisions\n .filter(decision => decision.workItemId === item.id)\n .map(summarizeDecision)\n .slice(-MAX_LIST),\n audit: audit.events\n .filter(event => event.targets.some(target => target.type === 'work_item' && target.id === item.id))\n .slice(0, 25)\n .map(event => ({\n id: event.id,\n action: event.action,\n actorId: event.actorId,\n actorType: event.actorType,\n occurredAt: iso(event.occurredAt),\n metadata: event.metadata,\n })),\n feed: feed.map(comment => ({\n id: comment.id,\n kind: comment.kind,\n author: comment.author,\n occurredAt: iso(comment.occurredAt),\n body: truncate(comment.body, MAX_TEXT),\n })),\n parent: parent ? summarizeItem(parent) : null,\n children: children.map(summarizeItem),\n };\n },\n }),\n\n factory_list_attention: createTool({\n id: 'factory_list_attention',\n description:\n 'Failed decisions grouped by failure code and error text, so a batch of cards that broke the same way reads as one incident. Also lists proposals waiting on a person and terminal cards that still hold seats.',\n inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_LIST).default(25) }).strict(),\n execute: async ({ limit }) => {\n const [decisions, items] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n deps.workItems.list(scope),\n ]);\n const itemsById = new Map(items.map(item => [item.id, item]));\n const groups = new Map<\n string,\n { failureCode: string; error: string; decisions: FactoryDeferredDecisionRecord[] }\n >();\n for (const decision of decisions) {\n if (decision.status !== 'failed') continue;\n const error = truncate(decision.lastError ?? '', 160);\n const key = `${decision.failureCode ?? 'unknown'}|${error}`;\n const group = groups.get(key) ?? { failureCode: decision.failureCode ?? 'unknown', error, decisions: [] };\n group.decisions.push(decision);\n groups.set(key, group);\n }\n const incidents = [...groups.values()]\n .sort((a, b) => b.decisions.length - a.decisions.length)\n .slice(0, limit)\n .map(group => ({\n failureCode: group.failureCode,\n failureLabel: factoryDispatchFailureMetadata(group.decisions[0]!.failureCode).label,\n error: group.error,\n count: group.decisions.length,\n firstFailedAt: iso(\n new Date(\n group.decisions.reduce(\n (earliest, decision) => Math.min(earliest, decision.updatedAt.getTime()),\n Infinity,\n ),\n ),\n ),\n lastFailedAt: iso(\n new Date(\n group.decisions.reduce((latest, decision) => Math.max(latest, decision.updatedAt.getTime()), -Infinity),\n ),\n ),\n decisions: group.decisions.slice(0, limit).map(decision => {\n const item = decision.workItemId ? itemsById.get(decision.workItemId) : undefined;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n number: item ? itemNumber(item) : null,\n title: item?.title ?? null,\n attempts: decision.attempts,\n };\n }),\n }));\n return {\n incidents,\n proposals: decisions\n .filter(d => d.status === 'proposed')\n .slice(0, limit)\n .map(summarizeDecision),\n };\n },\n }),\n\n factory_read_session: createTool({\n id: 'factory_read_session',\n description:\n \"The most recent turns of a card's shared agent thread: who spoke, when, the text (truncated) and which tools were called. Use to see what a worker actually did. Give a work item (id or number) or a threadId.\",\n inputSchema: z\n .object({\n id: z.string().uuid().optional(),\n number: z.number().int().positive().optional(),\n threadId: z.string().min(1).optional(),\n limit: z.number().int().min(1).max(40).default(20),\n })\n .strict()\n .refine(ref => ref.id !== undefined || ref.number !== undefined || ref.threadId !== undefined, {\n message: 'Provide a work item id, a number, or a threadId.',\n }),\n execute: async ({ limit, ...ref }) => {\n if (!deps.messageReader) throw new Error('Session transcripts are not available on this deployment.');\n let threadId = ref.threadId;\n let resourceId: string | undefined;\n if (!threadId) {\n const item = await findItem(deps, ref);\n if (!item) throw new Error(`No work item matches ${ref.id ?? `#${ref.number}`} in this Factory.`);\n const session = Object.values(item.sessions)[0];\n if (!session) return { threadId: null, item: summarizeItem(item), turns: [] };\n threadId = session.threadId;\n resourceId = session.sessionId;\n } else {\n // A thread the caller named must still belong to a card in this Factory.\n const bindings = await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId);\n const binding = bindings.find(candidate => candidate.threadId === threadId);\n if (!binding) throw new Error(`Thread ${threadId} is not bound to a card in this Factory.`);\n resourceId = binding.resourceId;\n }\n const page = await deps.messageReader.listMessages({\n threadId,\n ...(resourceId ? { resourceId } : {}),\n page: 0,\n perPage: limit,\n orderBy: { field: 'createdAt', direction: 'DESC' },\n });\n const turns = [...page.messages].reverse().map(message => ({\n id: message.id,\n role: message.role,\n createdAt: iso(message.createdAt),\n text: truncate(messageText(message), MAX_TEXT),\n tools: messageToolCalls(message),\n }));\n return { threadId, hasOlder: page.hasMore, turns };\n },\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"read-tools.js","names":[],"sources":["../../src/supervisor/read-tools.ts"],"sourcesContent":["/**\n * Supervisor read surface. Every tool is bounded (hard row caps, truncated\n * text) and answers with ids a person can click on the board, so the model\n * can explain a card's state without ever touching the database itself.\n */\n\nimport type { MastraDBMessage } from '@mastra/core/agent/message-list';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport { FACTORY_RULE_STAGES, factoryRuleStage } from '../rules/types.js';\nimport type { FactoryRuleStage } from '../rules/types.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryDeferredDecisionRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryHealthReport, FactoryHealthThresholds } from './health.js';\nimport { runFactoryHealthCheck } from './health.js';\n\nexport interface SupervisorScope {\n orgId: string;\n factoryProjectId: string;\n}\n\nexport interface SupervisorMessageReader {\n listMessages(input: {\n threadId: string;\n resourceId?: string;\n page: number;\n perPage: number;\n orderBy: { field: 'createdAt'; direction: 'ASC' | 'DESC' };\n }): Promise<{ messages: MastraDBMessage[]; hasMore: boolean }>;\n}\n\nexport interface SupervisorReadDependencies {\n scope: SupervisorScope;\n workItems: WorkItemsStorage;\n boards: BoardRegistry;\n comments: WorkItemCommentsStorage;\n audit: AuditStorage;\n messageReader?: SupervisorMessageReader;\n healthThresholds?: FactoryHealthThresholds;\n now?: () => Date;\n}\n\nconst MAX_TEXT = 600;\nconst MAX_ERROR = 400;\nconst MAX_LIST = 50;\n\nfunction truncate(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\nfunction iso(value: Date | string | null | undefined): string | null {\n if (!value) return null;\n const date = value instanceof Date ? value : new Date(value);\n return Number.isFinite(date.getTime()) ? date.toISOString() : null;\n}\n\nfunction itemNumber(item: WorkItemRow): number | null {\n const number = item.metadata?.number ?? item.metadata?.githubIssueNumber ?? item.metadata?.githubPullRequestNumber;\n return typeof number === 'number' ? number : null;\n}\n\nfunction itemLabels(item: WorkItemRow): string[] {\n const labels = item.metadata?.labels;\n return Array.isArray(labels) ? labels.filter((label): label is string => typeof label === 'string') : [];\n}\n\nfunction summarizeItem(item: WorkItemRow) {\n return {\n id: item.id,\n number: itemNumber(item),\n title: item.title,\n stage: factoryRuleStage(item.stages) ?? item.stages.join('+'),\n source: item.externalSource ? `${item.externalSource.integrationId}:${item.externalSource.type}` : 'manual',\n url: typeof item.metadata?.url === 'string' ? item.metadata.url : null,\n triageType: item.triageType,\n acceptedAt: iso(item.acceptedAt),\n autonomyArmedAt: iso(item.autonomyArmedAt),\n parentWorkItemId: item.parentWorkItemId,\n revision: item.revision,\n updatedAt: iso(item.updatedAt),\n };\n}\n\nfunction summarizeDecision(decision: FactoryDeferredDecisionRecord, overtaken = false) {\n const type = typeof decision.decision.type === 'string' ? decision.decision.type : 'decision';\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : null;\n const skill = typeof decision.decision.skillName === 'string' ? decision.decision.skillName : null;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n type,\n role,\n skill,\n status: decision.status,\n attempts: decision.attempts,\n availableAt: iso(decision.availableAt),\n leaseOwner: decision.leaseOwner,\n leaseExpiresAt: iso(decision.leaseExpiresAt),\n failureCode: decision.failureCode,\n failureLabel: decision.failureCode ? factoryDispatchFailureMetadata(decision.failureCode).label : null,\n canRetry: decisionRetryable(decision, overtaken),\n lastError: decision.lastError ? truncate(decision.lastError, MAX_ERROR) : null,\n approvedBy: decision.approvedBy,\n createdAt: iso(decision.createdAt),\n updatedAt: iso(decision.updatedAt),\n completedAt: iso(decision.completedAt),\n };\n}\n\nfunction messageParts(content: unknown): unknown[] {\n if (Array.isArray(content)) return content;\n if (content && typeof content === 'object') {\n const parts = (content as { parts?: unknown }).parts;\n if (Array.isArray(parts)) return parts;\n }\n return [];\n}\n\nfunction messageText(message: MastraDBMessage): string {\n const content: unknown = message.content;\n const parts = messageParts(content);\n const text: string[] = [];\n for (const part of parts) {\n if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'text') {\n const value = (part as { text?: unknown }).text;\n if (typeof value === 'string') text.push(value);\n }\n }\n if (text.length === 0 && typeof content === 'string') text.push(content);\n return text.join('\\n');\n}\n\nfunction messageToolCalls(message: MastraDBMessage): Array<{ tool: string; state: string | null }> {\n const parts = messageParts(message.content);\n const calls: Array<{ tool: string; state: string | null }> = [];\n for (const rawPart of parts) {\n if (!rawPart || typeof rawPart !== 'object') continue;\n const part = rawPart as Record<string, unknown>;\n const invocation =\n part.type === 'tool-invocation' && part.toolInvocation && typeof part.toolInvocation === 'object'\n ? (part.toolInvocation as Record<string, unknown>)\n : typeof part.type === 'string' && part.type.startsWith('tool-')\n ? part\n : undefined;\n if (!invocation) continue;\n const name = invocation.toolName ?? invocation.name ?? (typeof part.type === 'string' ? part.type.slice(5) : null);\n if (typeof name !== 'string') continue;\n calls.push({ tool: name, state: typeof invocation.state === 'string' ? invocation.state : null });\n }\n return calls;\n}\n\nasync function findItem(\n deps: SupervisorReadDependencies,\n ref: { id?: string; number?: number },\n): Promise<WorkItemRow | null> {\n if (ref.id) return deps.workItems.getForProject(deps.scope.orgId, deps.scope.factoryProjectId, ref.id);\n if (ref.number === undefined) return null;\n const items = await deps.workItems.list(deps.scope);\n const matches = items.filter(item => itemNumber(item) === ref.number);\n // Issue and PR numbers share a space on GitHub; prefer the Work-board card.\n return matches.find(item => item.externalSource?.type !== 'pull-request') ?? matches[0] ?? null;\n}\n\nconst itemRefSchema = z\n .object({\n id: z.string().uuid().optional().describe('Work item id.'),\n number: z.number().int().positive().optional().describe('Card number as shown on the board, e.g. 22874.'),\n })\n .refine(ref => ref.id !== undefined || ref.number !== undefined, { message: 'Provide an id or a number.' });\n\nexport function createFactorySupervisorReadTools(deps: SupervisorReadDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const scope = deps.scope;\n\n return {\n factory_overview: createTool({\n id: 'factory_overview',\n description:\n 'Counts per pipeline stage, decisions by status, active seats, open proposals and held cards for this Factory. Start here for \"what needs me\" questions.',\n inputSchema: z.object({}).strict(),\n execute: async () => {\n const [items, decisions, bindings, pendingStarts] = await Promise.all([\n deps.workItems.list(scope),\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId),\n deps.workItems.listPendingStarts(scope.orgId, scope.factoryProjectId),\n ]);\n const stages = Object.fromEntries(FACTORY_RULE_STAGES.map(stage => [stage, 0])) as Record<\n FactoryRuleStage,\n number\n >;\n let held = 0;\n for (const item of items) {\n const stage = factoryRuleStage(item.stages);\n if (stage) stages[stage] += 1;\n if (stage === 'triage' && item.triageType && item.triageType !== 'bug' && !item.acceptedAt) held += 1;\n }\n const decisionsByStatus: Record<string, number> = {};\n for (const decision of decisions) {\n decisionsByStatus[decision.status] = (decisionsByStatus[decision.status] ?? 0) + 1;\n }\n const failed = decisions.filter(d => d.status === 'failed').slice(0, MAX_LIST);\n const overtaken = await overtakenDecisionIds(deps.workItems, deps.boards, scope, failed);\n const activeSeats = bindings.filter(binding => binding.status === 'active');\n const seatsByRole: Record<string, number> = {};\n for (const seat of activeSeats) seatsByRole[seat.role] = (seatsByRole[seat.role] ?? 0) + 1;\n return {\n checkedAt: now().toISOString(),\n workItems: { total: items.length, byStage: stages, heldForDecision: held },\n decisions: decisionsByStatus,\n openProposals: decisions\n .filter(d => d.status === 'proposed')\n .map(decision => summarizeDecision(decision))\n .slice(0, MAX_LIST),\n failedDecisions: failed.map(decision => summarizeDecision(decision, overtaken.has(decision.id))),\n activeSeats: { total: activeSeats.length, byRole: seatsByRole },\n pendingStarts: pendingStarts.filter(start => start.status !== 'sent').length,\n };\n },\n }),\n\n factory_health_check: createTool({\n id: 'factory_health_check',\n description:\n 'Deterministic list of things wrong with this Factory right now (stuck decisions, stalled starts, orphaned or missing seats, held cards waiting on a person, label drift). Each finding carries evidence and the standard repair. Explain these; do not invent findings that are not listed.',\n inputSchema: z.object({}).strict(),\n execute: async (): Promise<FactoryHealthReport> =>\n runFactoryHealthCheck(deps.workItems, deps.boards, scope, { now: now(), thresholds: deps.healthThresholds }),\n }),\n\n factory_inspect_work_item: createTool({\n id: 'factory_inspect_work_item',\n description:\n 'Everything the Factory knows about one card: row, stage history, seats (run bindings), decisions with errors, recent audit events, recent feed comments, linked parent/children and last observed labels. Use for \"why is #N in this state\".',\n inputSchema: itemRefSchema,\n execute: async ref => {\n const item = await findItem(deps, ref);\n if (!item) throw new Error(`No work item matches ${ref.id ?? `#${ref.number}`} in this Factory.`);\n const [decisions, bindings, audit, feed, all] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, { workItemId: item.id }),\n deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, item.id),\n deps.audit.list({ orgId: scope.orgId, factoryProjectId: scope.factoryProjectId, limit: 200 }),\n deps.comments.listRecent({ ...scope, workItemId: item.id, limit: 10 }),\n deps.workItems.list(scope),\n ]);\n const parent = item.parentWorkItemId\n ? all.find(candidate => candidate.id === item.parentWorkItemId)\n : undefined;\n const children = all.filter(candidate => candidate.parentWorkItemId === item.id);\n const itemDecisions = decisions.filter(decision => decision.workItemId === item.id).slice(-MAX_LIST);\n const overtaken = await overtakenDecisionIds(\n deps.workItems,\n deps.boards,\n scope,\n itemDecisions.filter(decision => decision.status === 'failed'),\n );\n return {\n item: summarizeItem(item),\n labels: itemLabels(item),\n stageHistory: item.stageHistory.map(entry => ({\n stage: entry.stage,\n enteredAt: entry.enteredAt,\n exitedAt: entry.exitedAt ?? null,\n by: entry.by,\n })),\n sessions: Object.entries(item.sessions).map(([role, ref]) => ({\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n branch: ref.branch,\n startedBy: ref.startedBy,\n })),\n seats: bindings.map(binding => ({\n id: binding.id,\n role: binding.role,\n status: binding.status,\n sessionId: binding.sessionId,\n threadId: binding.threadId,\n createdAt: iso(binding.createdAt),\n revokedAt: iso(binding.revokedAt),\n })),\n decisions: itemDecisions.map(decision => summarizeDecision(decision, overtaken.has(decision.id))),\n audit: audit.events\n .filter(event => event.targets.some(target => target.type === 'work_item' && target.id === item.id))\n .slice(0, 25)\n .map(event => ({\n id: event.id,\n action: event.action,\n actorId: event.actorId,\n actorType: event.actorType,\n occurredAt: iso(event.occurredAt),\n metadata: event.metadata,\n })),\n feed: feed.map(comment => ({\n id: comment.id,\n kind: comment.kind,\n author: comment.author,\n occurredAt: iso(comment.occurredAt),\n body: truncate(comment.body, MAX_TEXT),\n })),\n parent: parent ? summarizeItem(parent) : null,\n children: children.map(summarizeItem),\n };\n },\n }),\n\n factory_list_attention: createTool({\n id: 'factory_list_attention',\n description:\n 'Failed decisions grouped by failure code and error text, so a batch of cards that broke the same way reads as one incident. Also lists proposals waiting on a person and terminal cards that still hold seats.',\n inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_LIST).default(25) }).strict(),\n execute: async ({ limit }) => {\n const [decisions, items] = await Promise.all([\n deps.workItems.listDeferredDecisions(scope.orgId, scope.factoryProjectId, {\n statuses: ['failed', 'proposed'],\n }),\n deps.workItems.list(scope),\n ]);\n const itemsById = new Map(items.map(item => [item.id, item]));\n const groups = new Map<\n string,\n { failureCode: string; error: string; decisions: FactoryDeferredDecisionRecord[] }\n >();\n for (const decision of decisions) {\n if (decision.status !== 'failed') continue;\n const error = truncate(decision.lastError ?? '', 160);\n const key = `${decision.failureCode ?? 'unknown'}|${error}`;\n const group = groups.get(key) ?? { failureCode: decision.failureCode ?? 'unknown', error, decisions: [] };\n group.decisions.push(decision);\n groups.set(key, group);\n }\n const incidents = [...groups.values()]\n .sort((a, b) => b.decisions.length - a.decisions.length)\n .slice(0, limit)\n .map(group => ({\n failureCode: group.failureCode,\n failureLabel: factoryDispatchFailureMetadata(group.decisions[0]!.failureCode).label,\n error: group.error,\n count: group.decisions.length,\n firstFailedAt: iso(\n new Date(\n group.decisions.reduce(\n (earliest, decision) => Math.min(earliest, decision.updatedAt.getTime()),\n Infinity,\n ),\n ),\n ),\n lastFailedAt: iso(\n new Date(\n group.decisions.reduce((latest, decision) => Math.max(latest, decision.updatedAt.getTime()), -Infinity),\n ),\n ),\n decisions: group.decisions.slice(0, limit).map(decision => {\n const item = decision.workItemId ? itemsById.get(decision.workItemId) : undefined;\n return {\n id: decision.id,\n workItemId: decision.workItemId,\n number: item ? itemNumber(item) : null,\n title: item?.title ?? null,\n attempts: decision.attempts,\n };\n }),\n }));\n return {\n incidents,\n proposals: decisions\n .filter(d => d.status === 'proposed')\n .slice(0, limit)\n .map(decision => summarizeDecision(decision)),\n };\n },\n }),\n\n factory_read_session: createTool({\n id: 'factory_read_session',\n description:\n \"The most recent turns of a card's shared agent thread: who spoke, when, the text (truncated) and which tools were called. Use to see what a worker actually did. Give a work item (id or number) or a threadId.\",\n inputSchema: z\n .object({\n id: z.string().uuid().optional(),\n number: z.number().int().positive().optional(),\n threadId: z.string().min(1).optional(),\n limit: z.number().int().min(1).max(40).default(20),\n })\n .strict()\n .refine(ref => ref.id !== undefined || ref.number !== undefined || ref.threadId !== undefined, {\n message: 'Provide a work item id, a number, or a threadId.',\n }),\n execute: async ({ limit, ...ref }) => {\n if (!deps.messageReader) throw new Error('Session transcripts are not available on this deployment.');\n let threadId = ref.threadId;\n let resourceId: string | undefined;\n if (!threadId) {\n const item = await findItem(deps, ref);\n if (!item) throw new Error(`No work item matches ${ref.id ?? `#${ref.number}`} in this Factory.`);\n const session = Object.values(item.sessions)[0];\n if (!session) return { threadId: null, item: summarizeItem(item), turns: [] };\n threadId = session.threadId;\n resourceId = session.sessionId;\n } else {\n // A thread the caller named must still belong to a card in this Factory.\n const bindings = await deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId);\n const binding = bindings.find(candidate => candidate.threadId === threadId);\n if (!binding) throw new Error(`Thread ${threadId} is not bound to a card in this Factory.`);\n resourceId = binding.resourceId;\n }\n const page = await deps.messageReader.listMessages({\n threadId,\n ...(resourceId ? { resourceId } : {}),\n page: 0,\n perPage: limit,\n orderBy: { field: 'createdAt', direction: 'DESC' },\n });\n const turns = [...page.messages].reverse().map(message => ({\n id: message.id,\n role: message.role,\n createdAt: iso(message.createdAt),\n text: truncate(messageText(message), MAX_TEXT),\n tools: messageToolCalls(message),\n }));\n return { threadId, hasOlder: page.hasMore, turns };\n },\n }),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,UAAU,2BAA2B;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;KAC5B,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CAoNrG"}
1
+ {"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,UAAU,2BAA2B;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;KAC5B,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA6NrG"}
@@ -1,6 +1,7 @@
1
1
  import { BOARD_IDENTIFIER_RE } from "../rules/validation.js";
2
2
  import { boardForWorkItem } from "../boards/semantics.js";
3
3
  import "../boards/index.js";
4
+ import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
4
5
  import { overtakenDecisionIds } from "../rules/decision-applicability.js";
5
6
  import { createTool } from "@mastra/core/tools";
6
7
  import { z } from "zod";
@@ -28,7 +29,10 @@ function createFactorySupervisorWriteTools(deps) {
28
29
  requireApproval: true,
29
30
  execute: async ({ decisionId }) => {
30
31
  const current = await deps.workItems.getDeferredDecision(deps.scope.orgId, deps.scope.factoryProjectId, decisionId);
31
- if (current && (await overtakenDecisionIds(deps.workItems, deps.boards, deps.scope, [current])).size > 0) throw new Error("The work item has moved on from the phase this run was decided for; it cannot be retried.");
32
+ if (!current || current.status !== "failed") throw new Error("The decision is not failed, no longer exists, or belongs to another factory.");
33
+ const failure = factoryDispatchFailureMetadata(current.failureCode);
34
+ if (!failure.canRetry) throw new Error(`This failure (${failure.label}) cannot be fixed by a retry; repair the cause and start a new run instead.`);
35
+ if ((await overtakenDecisionIds(deps.workItems, deps.boards, deps.scope, [current])).size > 0) throw new Error("The work item has moved on from the phase this run was decided for; it cannot be retried.");
32
36
  const decision = await deps.workItems.retryDeferredDecision(deps.scope.orgId, deps.scope.factoryProjectId, decisionId, now());
33
37
  if (!decision) throw new Error("The decision is not failed, no longer exists, or belongs to another factory.");
34
38
  await audit("factory.run.retry", {