@mastra/factory 0.13.0-alpha.4 → 0.13.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boards/define-board.d.ts +39 -0
- package/dist/boards/define-board.d.ts.map +1 -0
- package/dist/boards/define-board.js +51 -0
- package/dist/boards/define-board.js.map +1 -0
- package/dist/boards/index.d.ts +10 -0
- package/dist/boards/index.d.ts.map +1 -0
- package/dist/boards/index.js +19 -0
- package/dist/boards/index.js.map +1 -0
- package/dist/boards/review.d.ts +4 -0
- package/dist/boards/review.d.ts.map +1 -0
- package/dist/boards/review.js +76 -0
- package/dist/boards/review.js.map +1 -0
- package/dist/boards/work.d.ts +4 -0
- package/dist/boards/work.d.ts.map +1 -0
- package/dist/boards/work.js +152 -0
- package/dist/boards/work.js.map +1 -0
- package/dist/factory.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/integrations/github/acceptance-labels.d.ts +0 -1
- package/dist/integrations/github/acceptance-labels.d.ts.map +1 -1
- package/dist/integrations/github/acceptance-labels.js +2 -2
- package/dist/integrations/github/acceptance-labels.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +2 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +2 -1
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/linear/agent-tools.d.ts +3 -3
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
- package/dist/integrations/linear/agent-tools.js +5 -3
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +1 -0
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/routes/attention-proposed.d.ts +3 -0
- package/dist/routes/attention-proposed.d.ts.map +1 -0
- package/dist/routes/attention-proposed.js +21 -0
- package/dist/routes/attention-proposed.js.map +1 -0
- package/dist/routes/attention-providers.d.ts +16 -4
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +53 -46
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js +22 -28
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +10 -6
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +9 -40
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +4 -109
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +5 -2
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.js +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +2 -1
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +3 -16
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +12 -43
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +14 -8
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +9 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +9 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +8 -18
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +52 -37
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/health.d.ts.map +1 -1
- package/dist/supervisor/health.js +1 -2
- package/dist/supervisor/health.js.map +1 -1
- package/dist/work-item-branch.d.ts +6 -0
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +15 -9
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.js +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/supervisor/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EACV,6BAA6B,EAC7B,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,GAChC,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,cAAc,GACd,kBAAkB,GAClB,cAAc,GACd,aAAa,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAClD;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,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,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,iFAAiF;IACjF,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,yBAAyB,EAAE,uBAKvC,CAAC;AAqDF,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;CAC5C;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,IAAI,EACT,UAAU,GAAE,uBAAmD,GAC9D,mBAAmB,CA2JrB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,gBAAgB,EAC3B,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,5 +1,4 @@
|
|
|
1
|
-
import { NEEDS_APPROVAL_LABEL } from "../
|
|
2
|
-
import { FACTORY_ROLE_STAGES, factoryRuleStage, isTerminalFactoryRuleStage } from "../rules/types.js";
|
|
1
|
+
import { FACTORY_ROLE_STAGES, NEEDS_APPROVAL_LABEL, factoryRuleStage, isTerminalFactoryRuleStage } from "../rules/types.js";
|
|
3
2
|
//#region src/supervisor/health.ts
|
|
4
3
|
/**
|
|
5
4
|
* Deterministic Factory health check.
|
|
@@ -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 { NEEDS_APPROVAL_LABEL } from '../integrations/github/acceptance-labels.js';\nimport { FACTORY_ROLE_STAGES, factoryRuleStage, isTerminalFactoryRuleStage } 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-failed'\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'proposal-waiting'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'retry-decision'; decisionId: string }\n | { action: 'dismiss-decision'; decisionId: string }\n | { action: 'resolve-proposal'; decisionId: string }\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 /** How long the condition has held, in ms, when it is age-based. */\n ageMs: number | 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 /** Proposals and held cards older than this are worth nudging a person 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-failed',\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'proposal-waiting',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\n/** Working lane → the role whose seat carries a card through it. */\nconst ROLE_FOR_LANE = new Map<string, string>(Object.entries(FACTORY_ROLE_STAGES).map(([role, lane]) => [lane, role]));\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\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\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 === 'failed') {\n findings.push({\n kind: 'decision-failed',\n id: `decision-failed:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) failed after ${decision.attempts} attempt(s) at ${decision.updatedAt.toISOString()}${decision.failureCode ? ` [${decision.failureCode}]` : ''}: ${truncate(decision.lastError ?? 'no error recorded')}`,\n ageMs: now.getTime() - decision.updatedAt.getTime(),\n suggestedRepair: { action: 'retry-decision', decisionId: decision.id },\n });\n continue;\n }\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 ageMs: overdue,\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 ageMs: expired,\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'proposed') {\n const waiting = now.getTime() - decision.createdAt.getTime();\n if (waiting > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'proposal-waiting',\n id: `proposal-waiting:${decision.id}`,\n ...base,\n evidence: `Proposed run ${decision.id} (${describeDecision(decision)}) has waited for a person since ${decision.createdAt.toISOString()}.`,\n ageMs: waiting,\n suggestedRepair: { action: 'resolve-proposal', decisionId: decision.id },\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 ageMs: age,\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 || !stage || isTerminalFactoryRuleStage([stage])) {\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 ageMs: now.getTime() - binding.createdAt.getTime(),\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 if (!stage || stage === 'intake' || isTerminalFactoryRuleStage([stage])) continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n const held = stage === 'triage' && item.triageType !== null && item.triageType !== 'bug' && !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 ageMs: inStageMs,\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 = ROLE_FOR_LANE.get(stage);\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 ageMs: inStageMs,\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 ageMs: now.getTime() - item.acceptedAt.getTime(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => (b.ageMs ?? 0) - (a.ageMs ?? 0));\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 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 },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":";;;;;;;;;;;;AAuEA,MAAa,4BAAqD;CAChE,iBAAiB,KAAK;CACtB,gBAAgB,IAAI;CACpB,gBAAgB,KAAK;CACrB,mBAAmB,OAAU;AAC/B;AAEA,MAAM,gBAA4C;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,8CAA8B,IAAI,IAAI;CAAC;CAAW;CAAS;CAAU;AAAU,CAAC;;AAGtF,MAAM,gBAAgB,IAAI,IAAoB,OAAO,QAAQ,mBAAmB,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM,IAAI,CAAC,CAAC;AAErH,SAAS,WAAW,MAA8C;CAChE,MAAM,SAAS,MAAM,UAAU,UAAU,MAAM,UAAU,qBAAqB,MAAM,UAAU;CAC9F,OAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AAEA,SAAS,WAAW,MAA6B;CAC/C,MAAM,SAAS,KAAK,UAAU;CAC9B,OAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IAAI,CAAC;AACzG;AAEA,SAAS,aAAa,UAAiD;CACrE,MAAM,OAAO,SAAS,SAAS;CAC/B,OAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;AAEA,SAAS,aAAa,UAAwD;CAC5E,MAAM,OAAO,SAAS,SAAS;CAC/B,OAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;AAEA,SAAS,iBAAiB,UAAiD;CACzE,MAAM,OAAO,aAAa,QAAQ;CAClC,OAAO,OAAO,GAAG,aAAa,QAAQ,EAAE,IAAI,KAAK,KAAK,aAAa,QAAQ;AAC7E;AAEA,SAAS,SAAS,MAAc,MAAM,KAAa;CACjD,OAAO,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK;AAC5D;AAEA,SAAS,eAAe,MAAgC;CACtD,MAAM,OAAO,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAK,UAAS,CAAC,MAAM,QAAQ;CAC3E,MAAM,KAAK,OAAO,KAAK,MAAM,KAAK,SAAS,IAAI;CAC/C,OAAO,OAAO,SAAS,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI;AAC9C;;AAUA,SAAgB,qBACd,QACA,KACA,aAAsC,2BACjB;CACrB,MAAM,YAAY,IAAI,IAAI,OAAO,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;CACnE,MAAM,WAAmC,CAAC;CAC1C,MAAM,WAAW,eAA8B;EAC7C,MAAM,OAAO,aAAa,UAAU,IAAI,UAAU,IAAI,KAAA;EACtD,OAAO;GAAE;GAAY,gBAAgB,WAAW,IAAI;GAAG,OAAO,MAAM,SAAS;EAAG;CAClF;CAEA,KAAK,MAAM,YAAY,OAAO,WAAW;EACvC,MAAM,OAAO,QAAQ,SAAS,UAAU;EACxC,IAAI,SAAS,WAAW,UAAU;GAChC,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,mBAAmB,SAAS;IAChC,GAAG;IACH,UAAU,YAAY,SAAS,GAAG,IAAI,iBAAiB,QAAQ,EAAE,iBAAiB,SAAS,SAAS,iBAAiB,SAAS,UAAU,YAAY,IAAI,SAAS,cAAc,KAAK,SAAS,YAAY,KAAK,GAAG,IAAI,SAAS,SAAS,aAAa,mBAAmB;IACvQ,OAAO,IAAI,QAAQ,IAAI,SAAS,UAAU,QAAQ;IAClD,iBAAiB;KAAE,QAAQ;KAAkB,YAAY,SAAS;IAAG;GACvE,CAAC;GACD;EACF;EACA,IAAI,SAAS,WAAW,WAAW,SAAS,WAAW,WAAW;GAChE,MAAM,UAAU,IAAI,QAAQ,IAAI,SAAS,YAAY,QAAQ;GAC7D,IAAI,UAAU,WAAW,iBACvB,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,kBAAkB,SAAS;IAC/B,GAAG;IACH,UAAU,YAAY,SAAS,GAAG,IAAI,iBAAiB,QAAQ,EAAE,aAAa,SAAS,OAAO,SAAS,SAAS,YAAY,YAAY,EAAE,QAAQ,SAAS,SAAS;IACpK,OAAO;IACP,iBAAiB;GACnB,CAAC;GAEH;EACF;EACA,IAAI,SAAS,WAAW,YAAY,SAAS,gBAAgB;GAC3D,MAAM,UAAU,IAAI,QAAQ,IAAI,SAAS,eAAe,QAAQ;GAChE,IAAI,UAAU,WAAW,gBACvB,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,kBAAkB,SAAS;IAC/B,GAAG;IACH,UAAU,YAAY,SAAS,GAAG,IAAI,iBAAiB,QAAQ,EAAE,uBAAuB,SAAS,cAAc,UAAU,+BAA+B,SAAS,eAAe,YAAY,EAAE;IAC9L,OAAO;IACP,iBAAiB;GACnB,CAAC;GAEH;EACF;EACA,IAAI,SAAS,WAAW,YAAY;GAClC,MAAM,UAAU,IAAI,QAAQ,IAAI,SAAS,UAAU,QAAQ;GAC3D,IAAI,UAAU,WAAW,mBACvB,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,oBAAoB,SAAS;IACjC,GAAG;IACH,UAAU,gBAAgB,SAAS,GAAG,IAAI,iBAAiB,QAAQ,EAAE,kCAAkC,SAAS,UAAU,YAAY,EAAE;IACxI,OAAO;IACP,iBAAiB;KAAE,QAAQ;KAAoB,YAAY,SAAS;IAAG;GACzE,CAAC;EAEL;CACF;CAEA,MAAM,eAAe,IAAI,IAAI,OAAO,SAAS,KAAI,YAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;CAClF,KAAK,MAAM,SAAS,OAAO,eAAe;EACxC,IAAI,MAAM,WAAW,QAAQ;EAC7B,MAAM,UAAU,aAAa,IAAI,MAAM,SAAS;EAChD,MAAM,OAAO,QAAQ,SAAS,cAAc,IAAI;EAChD,MAAM,MAAM,IAAI,QAAQ,IAAI,MAAM,UAAU,QAAQ;EACpD,IAAI,MAAM,WAAW,YAAY,MAAM,WAAW,gBAChD,SAAS,KAAK;GACZ,MAAM;GACN,IAAI,iBAAiB,MAAM;GAC3B,GAAG;GACH,UAAU,iBAAiB,MAAM,KAAK,UAAU,YAAY,QAAQ,KAAK,SAAS,GAAG,MAAM,MAAM,OAAO,SAAS,MAAM,UAAU,YAAY,EAAE,SAAS,MAAM,SAAS,aAAa,MAAM,YAAY,KAAK,SAAS,MAAM,SAAS,MAAM;GACzO,OAAO;GACP,iBAAiB,UAAU;IAAE,QAAQ;IAAkB,WAAW,QAAQ;GAAG,IAAI;EACnF,CAAC;CAEL;CAEA,MAAM,oCAAoB,IAAI,IAAuC;CACrE,KAAK,MAAM,WAAW,OAAO,UAAU;EACrC,IAAI,QAAQ,WAAW,UAAU;EACjC,MAAM,OAAO,UAAU,IAAI,QAAQ,UAAU;EAC7C,MAAM,QAAQ,OAAO,iBAAiB,KAAK,MAAM,IAAI,KAAA;EACrD,IAAI,CAAC,QAAQ,CAAC,SAAS,2BAA2B,CAAC,KAAK,CAAC,GAAG;GAC1D,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,iBAAiB,QAAQ;IAC7B,GAAG,QAAQ,OAAO,KAAK,KAAK,IAAI;IAChC,UAAU,OACN,WAAW,QAAQ,GAAG,IAAI,QAAQ,KAAK,0CAA0C,SAAS,KAAK,OAAO,KAAK,GAAG,EAAE,KAChH,WAAW,QAAQ,GAAG,IAAI,QAAQ,KAAK,4BAA4B,QAAQ,WAAW;IAC1F,OAAO,IAAI,QAAQ,IAAI,QAAQ,UAAU,QAAQ;IACjD,iBAAiB;KAAE,QAAQ;KAAkB,WAAW,QAAQ;IAAG;GACrE,CAAC;GACD;EACF;EACA,MAAM,QAAQ,kBAAkB,IAAI,KAAK,EAAE,KAAK,CAAC;EACjD,MAAM,KAAK,OAAO;EAClB,kBAAkB,IAAI,KAAK,IAAI,KAAK;CACtC;CAEA,MAAM,iBAAiB,IAAI,IACzB,OAAO,UAAU,QAAO,MAAK,EAAE,cAAc,4BAA4B,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,KAAI,MAAK,EAAE,UAAW,CAChH;CACA,KAAK,MAAM,QAAQ,OAAO,OAAO;EAC/B,MAAM,QAAQ,iBAAiB,KAAK,MAAM;EAC1C,IAAI,CAAC,SAAS,UAAU,YAAY,2BAA2B,CAAC,KAAK,CAAC,GAAG;EACzE,MAAM,OAAO,QAAQ,KAAK,EAAE;EAC5B,MAAM,YAAY,eAAe,IAAI;EACrC,MAAM,YAAY,YAAY,IAAI,QAAQ,IAAI,UAAU,QAAQ,IAAI;EAGpE,IADa,UAAU,YAAY,KAAK,eAAe,QAAQ,KAAK,eAAe,SAAS,CAAC,KAAK,YAE5F;OAAA,cAAc,QAAQ,YAAY,WAAW,mBAC/C,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,gBAAgB,KAAK;IACzB,GAAG;IACH,UAAU,eAAe,KAAK,WAAW,kDAAkD,UAAW,YAAY,EAAE;IACpH,OAAO;IACP,iBAAiB;KAAE,QAAQ;KAAoB,YAAY,KAAK;IAAG;GACrE,CAAC;EAAA,OAEE,IAAI,CAAC,kBAAkB,IAAI,KAAK,EAAE,KAAK,CAAC,eAAe,IAAI,KAAK,EAAE,GAAG;GAC1E,MAAM,OAAO,cAAc,IAAI,KAAK;GACpC,SAAS,KAAK;IACZ,MAAM;IACN,IAAI,gBAAgB,KAAK;IACzB,GAAG;IACH,UAAU,MAAM,MAAM,SAAS,WAAW,YAAY,KAAK,UAAU;IACrE,OAAO;IACP,iBAAiB,OAAO;KAAE,QAAQ;KAAa,YAAY,KAAK;KAAI;IAAK,IAAI;GAC/E,CAAC;EACH;EAEA,IAAI,KAAK,cAAc,WAAW,IAAI,CAAC,CAAC,SAAA,wBAA6B,GACnE,SAAS,KAAK;GACZ,MAAM;GACN,IAAI,eAAe,KAAK;GACxB,GAAG;GACH,UAAU,eAAe,KAAK,WAAW,YAAY,EAAE,+CAA+C,qBAAqB;GAC3H,OAAO,IAAI,QAAQ,IAAI,KAAK,WAAW,QAAQ;GAC/C,iBAAiB;IAAE,QAAQ;IAAoB,YAAY,KAAK;GAAG;EACrE,CAAC;CAEL;CAEA,SAAS,MAAM,GAAG,OAAO,EAAE,SAAS,MAAM,EAAE,SAAS,EAAE;CACvD,MAAM,SAAS,OAAO,YAAY,cAAc,KAAI,SAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;CACtE,KAAK,MAAM,WAAW,UAAU,OAAO,QAAQ,SAAS;CACxD,OAAO;EAAE,WAAW,IAAI,YAAY;EAAG;EAAU;CAAO;AAC1D;AAEA,eAAsB,sBACpB,WACA,OACA,UAAgE,CAAC,GACnC;CAC9B,MAAM,CAAC,OAAO,WAAW,UAAU,iBAAiB,MAAM,QAAQ,IAAI;EACpE,UAAU,KAAK,KAAK;EACpB,UAAU,sBAAsB,MAAM,OAAO,MAAM,gBAAgB;EACnE,UAAU,gBAAgB,MAAM,OAAO,MAAM,gBAAgB;EAC7D,UAAU,kBAAkB,MAAM,OAAO,MAAM,gBAAgB;CACjE,CAAC;CACD,OAAO,qBACL;EAAE;EAAO;EAAW;EAAU;CAAc,GAC5C,QAAQ,uBAAO,IAAI,KAAK,GACxB,QAAQ,UACV;AACF"}
|
|
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 {\n FACTORY_ROLE_STAGES,\n factoryRuleStage,\n isTerminalFactoryRuleStage,\n NEEDS_APPROVAL_LABEL,\n} 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-failed'\n | 'decision-stuck'\n | 'start-stalled'\n | 'seat-orphaned'\n | 'seat-missing'\n | 'proposal-waiting'\n | 'held-waiting'\n | 'label-drift';\n\nexport type FactoryHealthRepair =\n | { action: 'retry-decision'; decisionId: string }\n | { action: 'dismiss-decision'; decisionId: string }\n | { action: 'resolve-proposal'; decisionId: string }\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 /** How long the condition has held, in ms, when it is age-based. */\n ageMs: number | 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 /** Proposals and held cards older than this are worth nudging a person 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-failed',\n 'decision-stuck',\n 'start-stalled',\n 'seat-orphaned',\n 'seat-missing',\n 'proposal-waiting',\n 'held-waiting',\n 'label-drift',\n];\n\nconst IN_FLIGHT_DECISION_STATUSES = new Set(['pending', 'retry', 'leased', 'proposed']);\n\n/** Working lane → the role whose seat carries a card through it. */\nconst ROLE_FOR_LANE = new Map<string, string>(Object.entries(FACTORY_ROLE_STAGES).map(([role, lane]) => [lane, role]));\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\ninterface HealthInputs {\n items: WorkItemRow[];\n decisions: FactoryDeferredDecisionRecord[];\n bindings: FactoryRunBindingRecord[];\n pendingStarts: FactoryPendingStartRecord[];\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 === 'failed') {\n findings.push({\n kind: 'decision-failed',\n id: `decision-failed:${decision.id}`,\n ...base,\n evidence: `Decision ${decision.id} (${describeDecision(decision)}) failed after ${decision.attempts} attempt(s) at ${decision.updatedAt.toISOString()}${decision.failureCode ? ` [${decision.failureCode}]` : ''}: ${truncate(decision.lastError ?? 'no error recorded')}`,\n ageMs: now.getTime() - decision.updatedAt.getTime(),\n suggestedRepair: { action: 'retry-decision', decisionId: decision.id },\n });\n continue;\n }\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 ageMs: overdue,\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 ageMs: expired,\n suggestedRepair: null,\n });\n }\n continue;\n }\n if (decision.status === 'proposed') {\n const waiting = now.getTime() - decision.createdAt.getTime();\n if (waiting > thresholds.waitingOnPersonMs) {\n findings.push({\n kind: 'proposal-waiting',\n id: `proposal-waiting:${decision.id}`,\n ...base,\n evidence: `Proposed run ${decision.id} (${describeDecision(decision)}) has waited for a person since ${decision.createdAt.toISOString()}.`,\n ageMs: waiting,\n suggestedRepair: { action: 'resolve-proposal', decisionId: decision.id },\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 ageMs: age,\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 || !stage || isTerminalFactoryRuleStage([stage])) {\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 ageMs: now.getTime() - binding.createdAt.getTime(),\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 if (!stage || stage === 'intake' || isTerminalFactoryRuleStage([stage])) continue;\n const base = subject(item.id);\n const enteredAt = stageEnteredAt(item);\n const inStageMs = enteredAt ? now.getTime() - enteredAt.getTime() : null;\n\n const held = stage === 'triage' && item.triageType !== null && item.triageType !== 'bug' && !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 ageMs: inStageMs,\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 = ROLE_FOR_LANE.get(stage);\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 ageMs: inStageMs,\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 ageMs: now.getTime() - item.acceptedAt.getTime(),\n suggestedRepair: { action: 'reconcile-labels', workItemId: item.id },\n });\n }\n }\n\n findings.sort((a, b) => (b.ageMs ?? 0) - (a.ageMs ?? 0));\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 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 },\n options.now ?? new Date(),\n options.thresholds,\n );\n}\n"],"mappings":"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"}
|
|
@@ -12,6 +12,12 @@ export interface WorkItemBranchInput {
|
|
|
12
12
|
}
|
|
13
13
|
/** Map a stored item's provenance onto the source vocabulary branch naming reads. */
|
|
14
14
|
export declare function workItemBranchSource(externalSource: ExternalWorkItemSource | null | undefined): WorkItemBranchSource;
|
|
15
|
+
/** The provider number a GitHub card carries — the `#12` its runs and thread titles name it by. */
|
|
16
|
+
export declare function workItemNumber(item: Pick<WorkItemBranchInput, 'source' | 'metadata'>): number | undefined;
|
|
17
|
+
/** How a card names its thread: a Linear title already opens with its identifier, a GitHub card gets its number here. */
|
|
18
|
+
export declare function workItemThreadTitle(item: Pick<WorkItemBranchInput, 'source' | 'metadata'> & {
|
|
19
|
+
title: string;
|
|
20
|
+
}): string;
|
|
15
21
|
/**
|
|
16
22
|
* The git branch an item's runs and sessions share, one grammar for both sides
|
|
17
23
|
* of the wire: the dispatcher names autonomous run branches with it and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-item-branch.d.ts","sourceRoot":"","sources":["../src/work-item-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE7G,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAOpH;AAOD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"work-item-branch.d.ts","sourceRoot":"","sources":["../src/work-item-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE7G,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAOpH;AAOD,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CAKzG;AAED,yHAAyH;AACzH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzE,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAWhE"}
|
package/dist/work-item-branch.js
CHANGED
|
@@ -10,6 +10,18 @@ function branchNumber(metadata, key) {
|
|
|
10
10
|
const value = metadata[key] ?? metadata.number;
|
|
11
11
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
12
12
|
}
|
|
13
|
+
/** The provider number a GitHub card carries — the `#12` its runs and thread titles name it by. */
|
|
14
|
+
function workItemNumber(item) {
|
|
15
|
+
const metadata = item.metadata ?? {};
|
|
16
|
+
if (item.source === "github-issue") return branchNumber(metadata, "githubIssueNumber");
|
|
17
|
+
if (item.source === "github-pr") return branchNumber(metadata, "githubPullRequestNumber");
|
|
18
|
+
}
|
|
19
|
+
/** How a card names its thread: a Linear title already opens with its identifier, a GitHub card gets its number here. */
|
|
20
|
+
function workItemThreadTitle(item) {
|
|
21
|
+
const number = workItemNumber(item);
|
|
22
|
+
if (number === void 0) return item.title;
|
|
23
|
+
return `${item.source === "github-pr" ? "PR" : "Issue"} #${number}: ${item.title}`;
|
|
24
|
+
}
|
|
13
25
|
/**
|
|
14
26
|
* The git branch an item's runs and sessions share, one grammar for both sides
|
|
15
27
|
* of the wire: the dispatcher names autonomous run branches with it and the
|
|
@@ -19,14 +31,8 @@ function branchNumber(metadata, key) {
|
|
|
19
31
|
*/
|
|
20
32
|
function workItemBranch(item) {
|
|
21
33
|
const metadata = item.metadata ?? {};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (issueNumber !== void 0) return `factory/issue-${issueNumber}`;
|
|
25
|
-
}
|
|
26
|
-
if (item.source === "github-pr") {
|
|
27
|
-
const pullRequestNumber = branchNumber(metadata, "githubPullRequestNumber");
|
|
28
|
-
if (pullRequestNumber !== void 0) return `factory/pr-${pullRequestNumber}`;
|
|
29
|
-
}
|
|
34
|
+
const githubNumber = workItemNumber(item);
|
|
35
|
+
if (githubNumber !== void 0) return item.source === "github-issue" ? `factory/issue-${githubNumber}` : `factory/pr-${githubNumber}`;
|
|
30
36
|
if (item.source === "linear-issue" && typeof metadata.identifier === "string") {
|
|
31
37
|
const identifier = metadata.identifier.trim();
|
|
32
38
|
if (identifier) return `factory/linear-${identifier.toLowerCase()}`;
|
|
@@ -34,6 +40,6 @@ function workItemBranch(item) {
|
|
|
34
40
|
return `factory/item-${item.id}`;
|
|
35
41
|
}
|
|
36
42
|
//#endregion
|
|
37
|
-
export { workItemBranch, workItemBranchSource };
|
|
43
|
+
export { workItemBranch, workItemBranchSource, workItemNumber, workItemThreadTitle };
|
|
38
44
|
|
|
39
45
|
//# sourceMappingURL=work-item-branch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-item-branch.js","names":[],"sources":["../src/work-item-branch.ts"],"sourcesContent":["import type { ExternalWorkItemSource } from './storage/domains/work-items/base.js';\n\n/**\n * Where a card came from, in the vocabulary branch naming reads. Server rows\n * map their `externalSource` into this with {@link workItemBranchSource}; the\n * board's own `WorkItem['source']` is already this union.\n */\nexport type WorkItemBranchSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'slack-thread' | 'manual';\n\nexport interface WorkItemBranchInput {\n id: string;\n source: WorkItemBranchSource;\n metadata?: Record<string, unknown> | null;\n}\n\n/** Map a stored item's provenance onto the source vocabulary branch naming reads. */\nexport function workItemBranchSource(externalSource: ExternalWorkItemSource | null | undefined): WorkItemBranchSource {\n if (!externalSource) return 'manual';\n if (externalSource.integrationId === 'linear') return 'linear-issue';\n // Only GitHub and Linear carry provider identities; anything else (a Slack\n // thread, say) is a plain work item rather than a mislabeled GitHub issue.\n if (externalSource.integrationId !== 'github') return 'manual';\n return externalSource.type === 'pull-request' ? 'github-pr' : 'github-issue';\n}\n\nfunction branchNumber(metadata: Record<string, unknown>, key: string): number | undefined {\n const value = metadata[key] ?? metadata.number;\n return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/**\n * The git branch an item's runs and sessions share, one grammar for both sides\n * of the wire: the dispatcher names autonomous run branches with it and the\n * board opens card sessions on it, so both converge on one checkout per item.\n * Cards without a provider identity (manual, Slack) and cards whose metadata\n * lost their identifier fall back to the id-derived branch.\n */\nexport function workItemBranch(item: WorkItemBranchInput): string {\n const metadata = item.metadata ?? {};\n
|
|
1
|
+
{"version":3,"file":"work-item-branch.js","names":[],"sources":["../src/work-item-branch.ts"],"sourcesContent":["import type { ExternalWorkItemSource } from './storage/domains/work-items/base.js';\n\n/**\n * Where a card came from, in the vocabulary branch naming reads. Server rows\n * map their `externalSource` into this with {@link workItemBranchSource}; the\n * board's own `WorkItem['source']` is already this union.\n */\nexport type WorkItemBranchSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'slack-thread' | 'manual';\n\nexport interface WorkItemBranchInput {\n id: string;\n source: WorkItemBranchSource;\n metadata?: Record<string, unknown> | null;\n}\n\n/** Map a stored item's provenance onto the source vocabulary branch naming reads. */\nexport function workItemBranchSource(externalSource: ExternalWorkItemSource | null | undefined): WorkItemBranchSource {\n if (!externalSource) return 'manual';\n if (externalSource.integrationId === 'linear') return 'linear-issue';\n // Only GitHub and Linear carry provider identities; anything else (a Slack\n // thread, say) is a plain work item rather than a mislabeled GitHub issue.\n if (externalSource.integrationId !== 'github') return 'manual';\n return externalSource.type === 'pull-request' ? 'github-pr' : 'github-issue';\n}\n\nfunction branchNumber(metadata: Record<string, unknown>, key: string): number | undefined {\n const value = metadata[key] ?? metadata.number;\n return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/** The provider number a GitHub card carries — the `#12` its runs and thread titles name it by. */\nexport function workItemNumber(item: Pick<WorkItemBranchInput, 'source' | 'metadata'>): number | undefined {\n const metadata = item.metadata ?? {};\n if (item.source === 'github-issue') return branchNumber(metadata, 'githubIssueNumber');\n if (item.source === 'github-pr') return branchNumber(metadata, 'githubPullRequestNumber');\n return;\n}\n\n/** How a card names its thread: a Linear title already opens with its identifier, a GitHub card gets its number here. */\nexport function workItemThreadTitle(\n item: Pick<WorkItemBranchInput, 'source' | 'metadata'> & { title: string },\n): string {\n const number = workItemNumber(item);\n if (number === undefined) return item.title;\n return `${item.source === 'github-pr' ? 'PR' : 'Issue'} #${number}: ${item.title}`;\n}\n\n/**\n * The git branch an item's runs and sessions share, one grammar for both sides\n * of the wire: the dispatcher names autonomous run branches with it and the\n * board opens card sessions on it, so both converge on one checkout per item.\n * Cards without a provider identity (manual, Slack) and cards whose metadata\n * lost their identifier fall back to the id-derived branch.\n */\nexport function workItemBranch(item: WorkItemBranchInput): string {\n const metadata = item.metadata ?? {};\n const githubNumber = workItemNumber(item);\n if (githubNumber !== undefined) {\n return item.source === 'github-issue' ? `factory/issue-${githubNumber}` : `factory/pr-${githubNumber}`;\n }\n if (item.source === 'linear-issue' && typeof metadata.identifier === 'string') {\n const identifier = metadata.identifier.trim();\n if (identifier) return `factory/linear-${identifier.toLowerCase()}`;\n }\n return `factory/item-${item.id}`;\n}\n"],"mappings":";;AAgBA,SAAgB,qBAAqB,gBAAiF;CACpH,IAAI,CAAC,gBAAgB,OAAO;CAC5B,IAAI,eAAe,kBAAkB,UAAU,OAAO;CAGtD,IAAI,eAAe,kBAAkB,UAAU,OAAO;CACtD,OAAO,eAAe,SAAS,iBAAiB,cAAc;AAChE;AAEA,SAAS,aAAa,UAAmC,KAAiC;CACxF,MAAM,QAAQ,SAAS,QAAQ,SAAS;CACxC,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,IAAI,QAAQ,KAAA;AACrF;;AAGA,SAAgB,eAAe,MAA4E;CACzG,MAAM,WAAW,KAAK,YAAY,CAAC;CACnC,IAAI,KAAK,WAAW,gBAAgB,OAAO,aAAa,UAAU,mBAAmB;CACrF,IAAI,KAAK,WAAW,aAAa,OAAO,aAAa,UAAU,yBAAyB;AAE1F;;AAGA,SAAgB,oBACd,MACQ;CACR,MAAM,SAAS,eAAe,IAAI;CAClC,IAAI,WAAW,KAAA,GAAW,OAAO,KAAK;CACtC,OAAO,GAAG,KAAK,WAAW,cAAc,OAAO,QAAQ,IAAI,OAAO,IAAI,KAAK;AAC7E;;;;;;;;AASA,SAAgB,eAAe,MAAmC;CAChE,MAAM,WAAW,KAAK,YAAY,CAAC;CACnC,MAAM,eAAe,eAAe,IAAI;CACxC,IAAI,iBAAiB,KAAA,GACnB,OAAO,KAAK,WAAW,iBAAiB,iBAAiB,iBAAiB,cAAc;CAE1F,IAAI,KAAK,WAAW,kBAAkB,OAAO,SAAS,eAAe,UAAU;EAC7E,MAAM,aAAa,SAAS,WAAW,KAAK;EAC5C,IAAI,YAAY,OAAO,kBAAkB,WAAW,YAAY;CAClE;CACA,OAAO,gBAAgB,KAAK;AAC9B"}
|
package/dist/workspace.js
CHANGED
|
@@ -8,9 +8,9 @@ import { createSessionSetupHook, evictSessionSandbox, getSessionSandbox, hasFail
|
|
|
8
8
|
import { getFactorySessionAddress } from "./rules/binding-context.js";
|
|
9
9
|
import { parseSupervisorResourceId } from "./supervisor/session.js";
|
|
10
10
|
import path, { dirname, join } from "path";
|
|
11
|
-
import { LocalSkillSource, Workspace } from "@mastra/core/workspace";
|
|
12
11
|
import { DEFAULT_CONFIG_DIR } from "@mastra/code-sdk/constants";
|
|
13
12
|
import { SandboxFilesystem } from "@mastra/code-sdk/agents/sandbox-filesystem";
|
|
13
|
+
import { LocalSkillSource, Workspace } from "@mastra/core/workspace";
|
|
14
14
|
import { existsSync } from "fs";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
16
|
import { MASTRACODE_WORKSPACE_TOOLS } from "@mastra/code-sdk/agents/tool-availability";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.13.0-alpha.
|
|
3
|
+
"version": "0.13.0-alpha.6",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
53
|
"@mastra/auth-studio": "1.3.5",
|
|
54
54
|
"@mastra/auth-workos": "1.6.5",
|
|
55
|
-
"@mastra/code-sdk": "1.7.0-alpha.
|
|
56
|
-
"@mastra/
|
|
57
|
-
"@mastra/
|
|
55
|
+
"@mastra/code-sdk": "1.7.0-alpha.5",
|
|
56
|
+
"@mastra/core": "1.65.0-alpha.4",
|
|
57
|
+
"@mastra/slack": "1.6.3"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "22.20.1",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"typescript": "^6.0.3",
|
|
64
64
|
"typescript-eslint": "^8.57.0",
|
|
65
65
|
"vitest": "4.1.10",
|
|
66
|
+
"@mastra/libsql": "1.22.4-alpha.0",
|
|
67
|
+
"@mastra/pg": "1.23.0-alpha.1",
|
|
66
68
|
"@internal/lint": "0.0.130",
|
|
67
|
-
"@mastra/libsql": "1.22.3",
|
|
68
69
|
"@internal/workspace": "0.0.2",
|
|
69
|
-
"@internal/types-builder": "0.0.105"
|
|
70
|
-
"@mastra/pg": "1.23.0-alpha.0"
|
|
70
|
+
"@internal/types-builder": "0.0.105"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=22.19.0"
|