@mastra/factory 0.15.1-alpha.0 → 0.16.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.
Files changed (92) hide show
  1. package/dist/boards/index.d.ts +1 -1
  2. package/dist/boards/index.d.ts.map +1 -1
  3. package/dist/boards/index.js +2 -2
  4. package/dist/boards/index.js.map +1 -1
  5. package/dist/boards/review.d.ts.map +1 -1
  6. package/dist/boards/review.js +4 -1
  7. package/dist/boards/review.js.map +1 -1
  8. package/dist/boards/semantics.d.ts +7 -0
  9. package/dist/boards/semantics.d.ts.map +1 -1
  10. package/dist/boards/semantics.js +15 -1
  11. package/dist/boards/semantics.js.map +1 -1
  12. package/dist/capabilities/intake.d.ts +4 -0
  13. package/dist/capabilities/intake.d.ts.map +1 -1
  14. package/dist/factory.d.ts.map +1 -1
  15. package/dist/factory.js +25 -11
  16. package/dist/factory.js.map +1 -1
  17. package/dist/integrations/linear/claim.d.ts +8 -0
  18. package/dist/integrations/linear/claim.d.ts.map +1 -0
  19. package/dist/integrations/linear/claim.js +14 -0
  20. package/dist/integrations/linear/claim.js.map +1 -0
  21. package/dist/integrations/linear/default-rules.d.ts +1 -0
  22. package/dist/integrations/linear/default-rules.d.ts.map +1 -1
  23. package/dist/integrations/linear/default-rules.js +2 -0
  24. package/dist/integrations/linear/default-rules.js.map +1 -1
  25. package/dist/integrations/linear/integration.d.ts +24 -6
  26. package/dist/integrations/linear/integration.d.ts.map +1 -1
  27. package/dist/integrations/linear/integration.js +257 -37
  28. package/dist/integrations/linear/integration.js.map +1 -1
  29. package/dist/integrations/linear/routes.d.ts +10 -0
  30. package/dist/integrations/linear/routes.d.ts.map +1 -1
  31. package/dist/integrations/linear/routes.js +74 -10
  32. package/dist/integrations/linear/routes.js.map +1 -1
  33. package/dist/integrations/linear/rules.d.ts +1 -1
  34. package/dist/integrations/linear/rules.d.ts.map +1 -1
  35. package/dist/integrations/linear/rules.js +27 -2
  36. package/dist/integrations/linear/rules.js.map +1 -1
  37. package/dist/integrations/platform/linear/integration.d.ts +34 -1
  38. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  39. package/dist/integrations/platform/linear/integration.js +271 -48
  40. package/dist/integrations/platform/linear/integration.js.map +1 -1
  41. package/dist/routes/intake.d.ts.map +1 -1
  42. package/dist/routes/intake.js +12 -2
  43. package/dist/routes/intake.js.map +1 -1
  44. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  45. package/dist/rules/dispatch-errors.js +8 -0
  46. package/dist/rules/dispatch-errors.js.map +1 -1
  47. package/dist/rules/dispatcher.d.ts +1 -0
  48. package/dist/rules/dispatcher.d.ts.map +1 -1
  49. package/dist/rules/dispatcher.js +179 -105
  50. package/dist/rules/dispatcher.js.map +1 -1
  51. package/dist/rules/start-coordinator.d.ts.map +1 -1
  52. package/dist/rules/start-coordinator.js +4 -1
  53. package/dist/rules/start-coordinator.js.map +1 -1
  54. package/dist/rules/terminal-cleanup.d.ts +14 -1
  55. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  56. package/dist/rules/terminal-cleanup.js +20 -0
  57. package/dist/rules/terminal-cleanup.js.map +1 -1
  58. package/dist/rules/tools.d.ts +2 -0
  59. package/dist/rules/tools.d.ts.map +1 -1
  60. package/dist/rules/tools.js +30 -2
  61. package/dist/rules/tools.js.map +1 -1
  62. package/dist/rules/transition-service.d.ts +3 -0
  63. package/dist/rules/transition-service.d.ts.map +1 -1
  64. package/dist/rules/transition-service.js +4 -2
  65. package/dist/rules/transition-service.js.map +1 -1
  66. package/dist/rules/types.d.ts +15 -0
  67. package/dist/rules/types.d.ts.map +1 -1
  68. package/dist/rules/types.js.map +1 -1
  69. package/dist/rules/validation.d.ts.map +1 -1
  70. package/dist/rules/validation.js +11 -2
  71. package/dist/rules/validation.js.map +1 -1
  72. package/dist/session/run-audit.d.ts +9 -0
  73. package/dist/session/run-audit.d.ts.map +1 -1
  74. package/dist/session/run-audit.js +37 -6
  75. package/dist/session/run-audit.js.map +1 -1
  76. package/dist/skills/service.d.ts +12 -0
  77. package/dist/skills/service.d.ts.map +1 -1
  78. package/dist/skills/service.js +27 -1
  79. package/dist/skills/service.js.map +1 -1
  80. package/dist/storage/domains/work-items/base.d.ts +41 -2
  81. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  82. package/dist/storage/domains/work-items/base.js +159 -12
  83. package/dist/storage/domains/work-items/base.js.map +1 -1
  84. package/dist/supervisor/session-messaging.d.ts +23 -0
  85. package/dist/supervisor/session-messaging.d.ts.map +1 -0
  86. package/dist/supervisor/session-messaging.js +15 -0
  87. package/dist/supervisor/session-messaging.js.map +1 -0
  88. package/dist/supervisor/write-tools.d.ts +2 -1
  89. package/dist/supervisor/write-tools.d.ts.map +1 -1
  90. package/dist/supervisor/write-tools.js +11 -7
  91. package/dist/supervisor/write-tools.js.map +1 -1
  92. package/package.json +10 -10
@@ -174,11 +174,13 @@ function validateFactoryRuleDecision(value, causalDepth = 0) {
174
174
  "board",
175
175
  "source",
176
176
  "sourceKey",
177
+ "claimKey",
177
178
  "title",
178
179
  "url",
179
180
  "stage",
180
181
  "metadata"
181
182
  ], "Factory linked work item decision");
183
+ const claimKey = optionalBoundedString(value.claimKey, "Factory linked work item claimKey", MAX_SOURCE_KEY_LENGTH);
182
184
  const url = value.url;
183
185
  if (url !== null && (typeof url !== "string" || url.length > MAX_URL_LENGTH || !/^https?:\/\//.test(url))) throw new FactoryRuleValidationError("Factory linked work item URL is invalid.");
184
186
  const metadata = sanitizeMetadata(value.metadata);
@@ -188,6 +190,7 @@ function validateFactoryRuleDecision(value, causalDepth = 0) {
188
190
  board: boardIdentifier(value.board, "Factory linked work item board"),
189
191
  source: enumValue(value.source, WORK_ITEM_SOURCES, "Factory linked work item source"),
190
192
  sourceKey: boundedString(value.sourceKey, "Factory linked work item sourceKey", MAX_SOURCE_KEY_LENGTH),
193
+ ...claimKey ? { claimKey } : {},
191
194
  title: boundedString(value.title, "Factory linked work item title", MAX_TITLE_LENGTH),
192
195
  url,
193
196
  stage: boardIdentifier(value.stage, "Factory linked work item stage"),
@@ -203,17 +206,23 @@ function validateFactoryRuleDecision(value, causalDepth = 0) {
203
206
  "prompt",
204
207
  "arguments",
205
208
  "precedingMessage",
206
- "cancelInFlight"
209
+ "cancelInFlight",
210
+ "resume"
207
211
  ], "Factory invoke skill decision");
208
212
  if (value.skillName === void 0 === (value.prompt === void 0)) throw new FactoryRuleValidationError("Factory skill invocation needs exactly one of skillName or prompt.");
209
213
  const args = optionalBoundedString(value.arguments, "Factory skill arguments", MAX_ARGUMENTS_LENGTH);
210
214
  const precedingMessage = optionalBoundedString(value.precedingMessage, "Factory skill preceding message", MAX_MESSAGE_LENGTH);
211
215
  if (value.cancelInFlight !== void 0 && typeof value.cancelInFlight !== "boolean") throw new FactoryRuleValidationError("Factory skill cancelInFlight must be a boolean.");
216
+ if (value.resume !== void 0 && typeof value.resume !== "boolean") throw new FactoryRuleValidationError("Factory skill resume must be a boolean.");
217
+ if (value.resume === true && value.skillName === void 0) throw new FactoryRuleValidationError("Factory skill resume requires skillName.");
212
218
  return {
213
219
  type,
214
220
  ...commonCommitFields(value),
215
221
  role: boundedString(value.role, "Factory skill role", 32, IDENTIFIER_RE),
216
- ...value.skillName === void 0 ? { prompt: boundedString(value.prompt, "Factory skill prompt", MAX_MESSAGE_LENGTH) } : { skillName: boundedString(value.skillName, "Factory skill name", MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) },
222
+ ...value.skillName === void 0 ? { prompt: boundedString(value.prompt, "Factory skill prompt", MAX_MESSAGE_LENGTH) } : {
223
+ skillName: boundedString(value.skillName, "Factory skill name", MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE),
224
+ ...value.resume === true ? { resume: true } : {}
225
+ },
217
226
  ...args ? { arguments: args } : {},
218
227
  ...precedingMessage ? { precedingMessage } : {},
219
228
  ...value.cancelInFlight === true ? { cancelInFlight: true } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import type { BoardRegistry } from '../boards/registry.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nexport const MAX_ROLE_LENGTH = 32;\nexport const MAX_TOOL_NAME_LENGTH = 128;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nexport const MAX_BOARD_IDENTIFIER_LENGTH = 128;\nexport const IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nexport const BOARD_IDENTIFIER_RE = IDENTIFIER_RE;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nexport function isBoardIdentifier(value: unknown): value is string {\n return typeof value === 'string' && value.length <= MAX_BOARD_IDENTIFIER_LENGTH && BOARD_IDENTIFIER_RE.test(value);\n}\n\nfunction boardIdentifier(value: unknown, label: string): string {\n if (!isBoardIdentifier(value)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value;\n}\n\nexport function assertFactoryDecisionTarget(\n decision: FactoryRuleDecision,\n boards: BoardRegistry,\n itemBoard?: string | null,\n): void {\n if (decision.type !== 'transition' && decision.type !== 'upsertLinkedWorkItem') return;\n const board = boards.get(decision.board);\n if (!board) throw new FactoryRuleValidationError('Factory decision target board is not installed.');\n if (!Object.hasOwn(board.phases, decision.stage)) {\n throw new FactoryRuleValidationError('Factory decision target phase is not defined on its board.');\n }\n if (decision.type === 'transition' && itemBoard !== undefined && itemBoard !== decision.board) {\n throw new FactoryRuleValidationError('Factory transition cannot change the item board.');\n }\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nexport const DEFAULT_FACTORY_CONFIG_VERSION = 'factory-config-v1';\n\n/** Operator-maintained provenance label stamped on transition audit and deferred-decision rows. */\nexport function assertFactoryConfigVersion(value: unknown): string {\n return boundedString(value, 'Factory configVersion', MAX_VERSION_LENGTH);\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: boardIdentifier(value.board, 'Factory transition board'),\n stage: boardIdentifier(value.stage, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: boardIdentifier(value.board, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: boardIdentifier(value.stage, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'skillName', 'prompt', 'arguments', 'precedingMessage', 'cancelInFlight'],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : { skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n if (value.prepareBinding === true && value.role === undefined) {\n throw new FactoryRuleValidationError('Factory message prepareBinding requires a role.');\n }\n const role =\n value.role === undefined\n ? undefined\n : boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n return {\n type,\n ...commonCommitFields(value),\n ...(role ? { role } : {}),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import type { BoardRegistry } from '../boards/registry.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nexport const MAX_ROLE_LENGTH = 32;\nexport const MAX_TOOL_NAME_LENGTH = 128;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nexport const MAX_BOARD_IDENTIFIER_LENGTH = 128;\nexport const IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nexport const BOARD_IDENTIFIER_RE = IDENTIFIER_RE;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nexport function isBoardIdentifier(value: unknown): value is string {\n return typeof value === 'string' && value.length <= MAX_BOARD_IDENTIFIER_LENGTH && BOARD_IDENTIFIER_RE.test(value);\n}\n\nfunction boardIdentifier(value: unknown, label: string): string {\n if (!isBoardIdentifier(value)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value;\n}\n\nexport function assertFactoryDecisionTarget(\n decision: FactoryRuleDecision,\n boards: BoardRegistry,\n itemBoard?: string | null,\n): void {\n if (decision.type !== 'transition' && decision.type !== 'upsertLinkedWorkItem') return;\n const board = boards.get(decision.board);\n if (!board) throw new FactoryRuleValidationError('Factory decision target board is not installed.');\n if (!Object.hasOwn(board.phases, decision.stage)) {\n throw new FactoryRuleValidationError('Factory decision target phase is not defined on its board.');\n }\n if (decision.type === 'transition' && itemBoard !== undefined && itemBoard !== decision.board) {\n throw new FactoryRuleValidationError('Factory transition cannot change the item board.');\n }\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nexport const DEFAULT_FACTORY_CONFIG_VERSION = 'factory-config-v1';\n\n/** Operator-maintained provenance label stamped on transition audit and deferred-decision rows. */\nexport function assertFactoryConfigVersion(value: unknown): string {\n return boundedString(value, 'Factory configVersion', MAX_VERSION_LENGTH);\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: boardIdentifier(value.board, 'Factory transition board'),\n stage: boardIdentifier(value.stage, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'claimKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const claimKey = optionalBoundedString(\n value.claimKey,\n 'Factory linked work item claimKey',\n MAX_SOURCE_KEY_LENGTH,\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: boardIdentifier(value.board, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n ...(claimKey ? { claimKey } : {}),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: boardIdentifier(value.stage, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n [\n 'type',\n 'idempotencyKey',\n 'role',\n 'skillName',\n 'prompt',\n 'arguments',\n 'precedingMessage',\n 'cancelInFlight',\n 'resume',\n ],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n if (value.resume !== undefined && typeof value.resume !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill resume must be a boolean.');\n }\n // Resume continues an already-active skill by name; a plain prompt run has no\n // skill to resume, so the dispatcher would silently ignore the flag.\n if (value.resume === true && value.skillName === undefined) {\n throw new FactoryRuleValidationError('Factory skill resume requires skillName.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : {\n skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE),\n ...(value.resume === true ? { resume: true } : {}),\n }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n if (value.prepareBinding === true && value.role === undefined) {\n throw new FactoryRuleValidationError('Factory message prepareBinding requires a role.');\n }\n const role =\n value.role === undefined\n ? undefined\n : boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n return {\n type,\n ...commonCommitFields(value),\n ...(role ? { role } : {}),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
@@ -16,8 +16,13 @@ declare const factoryOpenRunSchema: z.ZodObject<{
16
16
  threadId: z.ZodString;
17
17
  branch: z.ZodString;
18
18
  agentName: z.ZodString;
19
+ ownerId: z.ZodOptional<z.ZodString>;
20
+ heartbeatAt: z.ZodOptional<z.ZodNumber>;
19
21
  }, z.core.$strip>;
20
22
  export type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;
23
+ /** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */
24
+ export declare const FACTORY_OPEN_RUN_STALE_MS = 30000;
25
+ export declare function isOpenRunLiveElsewhere(run: FactoryOpenRun, ownerId: string, now?: number): boolean;
21
26
  export interface RunEndCaptureSession {
22
27
  readonly thread: {
23
28
  getSetting(args: {
@@ -36,12 +41,16 @@ export interface RunEndCaptureSession {
36
41
  type RunEndReason = Extract<AgentControllerEvent, {
37
42
  type: 'agent_end';
38
43
  }>['reason'];
44
+ export declare function listSessionOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]>;
45
+ export declare function waitForSessionRunAudit(session: RunEndCaptureSession): Promise<void>;
39
46
  export declare function recordSessionRunStart(session: RunEndCaptureSession, { audit, run, actorType, observedEnd, }: {
40
47
  audit: AuditRecorder;
41
48
  run: Omit<FactoryOpenRun, 'agentName'>;
42
49
  actorType: AuditActorType;
43
50
  observedEnd: () => RunEndReason;
44
51
  }): Promise<void>;
52
+ /** Renew this process's claim on an open run so other replicas keep treating it as live. */
53
+ export declare function heartbeatSessionOpenRun(session: RunEndCaptureSession, kickoffId: string, at?: number): Promise<void>;
45
54
  export declare function observeSessionRunEnd(session: RunEndCaptureSession, { audit }: {
46
55
  audit: AuditRecorder;
47
56
  }): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;iBAaxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,QAAQ,CAAC,IAAI,EAAE;QAAE,GAAG,IAAI,MAAM,CAAA;KAAE,CAAC;IACjC,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAuDnF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,EACE,KAAK,EACL,GAAG,EACH,SAAS,EACT,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IACvC,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,MAAM,YAAY,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,IAAI,CAKnH"}
1
+ {"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;iBAsBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAOtG;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,QAAQ,CAAC,IAAI,EAAE;QAAE,GAAG,IAAI,MAAM,CAAA;KAAE,CAAC;IACjC,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAWnF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGlG;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;AA4CD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,EACE,KAAK,EACL,GAAG,EACH,SAAS,EACT,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IACvC,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,MAAM,YAAY,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAef;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,EAAE,SAAa,GACd,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,IAAI,CAKnH"}
@@ -14,20 +14,37 @@ const factoryOpenRunSchema = z.object({
14
14
  sessionId: z.string(),
15
15
  threadId: z.string(),
16
16
  branch: z.string(),
17
- agentName: z.string()
17
+ agentName: z.string(),
18
+ /**
19
+ * Which dispatcher process is watching this run, and when it last said so
20
+ * (epoch ms). The ledger is a thread setting shared by every replica, so a
21
+ * remote reader needs these to tell a run that is live elsewhere from one
22
+ * that died with its process. Absent on entries written before ownership
23
+ * was recorded; those read as stale.
24
+ */
25
+ ownerId: z.string().optional(),
26
+ heartbeatAt: z.number().optional()
18
27
  });
28
+ /** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */
29
+ const FACTORY_OPEN_RUN_STALE_MS = 3e4;
30
+ function isOpenRunLiveElsewhere(run, ownerId, now = Date.now()) {
31
+ return run.ownerId !== void 0 && run.ownerId !== ownerId && run.heartbeatAt !== void 0 && now - run.heartbeatAt < 3e4;
32
+ }
19
33
  const pendingWrites = /* @__PURE__ */ new WeakMap();
20
34
  function serializeRunAudit(session, write) {
21
35
  const pending = (pendingWrites.get(session) ?? Promise.resolve()).then(write).catch((error) => console.warn("[Factory run audit] Unable to record run lifecycle.", error));
22
36
  pendingWrites.set(session, pending);
23
37
  return pending;
24
38
  }
25
- async function readOpenRuns(session) {
39
+ async function listSessionOpenRuns(session) {
26
40
  const stored = await session.thread.getSetting({ key: FACTORY_OPEN_RUNS_SETTING });
27
41
  return z.array(factoryOpenRunSchema).parse(stored ?? []);
28
42
  }
43
+ async function waitForSessionRunAudit(session) {
44
+ await (pendingWrites.get(session) ?? Promise.resolve());
45
+ }
29
46
  function runAuditInput(run) {
30
- const { orgId, factoryProjectId, workItemId, workItemName, ...metadata } = run;
47
+ const { orgId, factoryProjectId, workItemId, workItemName, ownerId, heartbeatAt, ...metadata } = run;
31
48
  return {
32
49
  orgId,
33
50
  factoryProjectId,
@@ -42,7 +59,7 @@ function runAuditInput(run) {
42
59
  }
43
60
  async function recordRunEnds(session, audit, reason, kickoffId) {
44
61
  if (!reason || reason === "suspended") return;
45
- const runs = await readOpenRuns(session);
62
+ const runs = await listSessionOpenRuns(session);
46
63
  if (runs.length === 0) return;
47
64
  const remaining = [];
48
65
  for (const run of runs) {
@@ -73,7 +90,7 @@ function recordSessionRunStart(session, { audit, run, actorType, observedEnd })
73
90
  agentName: auditAgentName(session.mode.get())
74
91
  };
75
92
  return serializeRunAudit(session, async () => {
76
- const runs = await readOpenRuns(session);
93
+ const runs = await listSessionOpenRuns(session);
77
94
  if (!runs.some((existing) => existing.kickoffId === run.kickoffId)) await session.thread.setSetting({
78
95
  key: FACTORY_OPEN_RUNS_SETTING,
79
96
  value: [...runs, openRun]
@@ -87,6 +104,20 @@ function recordSessionRunStart(session, { audit, run, actorType, observedEnd })
87
104
  await recordRunEnds(session, audit, observedEnd(), run.kickoffId);
88
105
  });
89
106
  }
107
+ /** Renew this process's claim on an open run so other replicas keep treating it as live. */
108
+ function heartbeatSessionOpenRun(session, kickoffId, at = Date.now()) {
109
+ return serializeRunAudit(session, async () => {
110
+ const runs = await listSessionOpenRuns(session);
111
+ if (!runs.some((run) => run.kickoffId === kickoffId)) return;
112
+ await session.thread.setSetting({
113
+ key: FACTORY_OPEN_RUNS_SETTING,
114
+ value: runs.map((run) => run.kickoffId === kickoffId ? {
115
+ ...run,
116
+ heartbeatAt: at
117
+ } : run)
118
+ });
119
+ });
120
+ }
90
121
  function observeSessionRunEnd(session, { audit }) {
91
122
  return session.subscribe((event) => {
92
123
  if (event.type !== "agent_end" || !event.reason || event.reason === "suspended") return;
@@ -94,6 +125,6 @@ function observeSessionRunEnd(session, { audit }) {
94
125
  });
95
126
  }
96
127
  //#endregion
97
- export { FACTORY_OPEN_RUNS_SETTING, observeSessionRunEnd, recordSessionRunStart };
128
+ export { FACTORY_OPEN_RUNS_SETTING, FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun, isOpenRunLiveElsewhere, listSessionOpenRuns, observeSessionRunEnd, recordSessionRunStart, waitForSessionRunAudit };
98
129
 
99
130
  //# sourceMappingURL=run-audit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-audit.js","names":[],"sources":["../../src/session/run-audit.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\nimport { z } from 'zod';\n\nimport { auditAgentName } from '../storage/domains/audit/base.js';\nimport type { AuditActorType } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\n\nexport const FACTORY_OPEN_RUNS_SETTING = 'factoryOpenRuns';\n\nconst factoryOpenRunSchema = z.object({\n kickoffId: z.string(),\n bindingId: z.string(),\n role: z.string(),\n startedBy: z.string(),\n orgId: z.string(),\n factoryProjectId: z.string(),\n workItemId: z.string(),\n workItemName: z.string().optional(),\n sessionId: z.string(),\n threadId: z.string(),\n branch: z.string(),\n agentName: z.string(),\n});\n\nexport type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;\n\nexport interface RunEndCaptureSession {\n readonly thread: {\n getSetting(args: { key: string }): Promise<unknown>;\n setSetting(args: { key: string; value: unknown }): Promise<void>;\n };\n readonly mode: { get(): string };\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\nconst pendingWrites = new WeakMap<RunEndCaptureSession, Promise<void>>();\n\nfunction serializeRunAudit(session: RunEndCaptureSession, write: () => Promise<void>): Promise<void> {\n const pending = (pendingWrites.get(session) ?? Promise.resolve())\n .then(write)\n .catch(error => console.warn('[Factory run audit] Unable to record run lifecycle.', error));\n pendingWrites.set(session, pending);\n return pending;\n}\n\nasync function readOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]> {\n const stored = await session.thread.getSetting({ key: FACTORY_OPEN_RUNS_SETTING });\n return z.array(factoryOpenRunSchema).parse(stored ?? []);\n}\n\nfunction runAuditInput(run: FactoryOpenRun) {\n const { orgId, factoryProjectId, workItemId, workItemName, ...metadata } = run;\n return {\n orgId,\n factoryProjectId,\n idempotencyKey: run.kickoffId,\n targets: [{ type: 'work_item', id: workItemId, ...(workItemName ? { name: workItemName } : {}) }],\n metadata,\n };\n}\n\nasync function recordRunEnds(\n session: RunEndCaptureSession,\n audit: AuditRecorder,\n reason: RunEndReason,\n kickoffId?: string,\n): Promise<void> {\n if (!reason || reason === 'suspended') return;\n const runs = await readOpenRuns(session);\n if (runs.length === 0) return;\n const remaining = [];\n for (const run of runs) {\n if (kickoffId && run.kickoffId !== kickoffId) {\n remaining.push(run);\n continue;\n }\n const input = runAuditInput(run);\n const recorded = await audit.record({\n ...input,\n actorId: `agent:${run.threadId}`,\n actorType: 'agent',\n action: 'factory.run.ended',\n metadata: { ...input.metadata, reason },\n });\n if (!recorded) remaining.push(run);\n }\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: remaining });\n}\n\nexport function recordSessionRunStart(\n session: RunEndCaptureSession,\n {\n audit,\n run,\n actorType,\n observedEnd,\n }: {\n audit: AuditRecorder;\n run: Omit<FactoryOpenRun, 'agentName'>;\n actorType: AuditActorType;\n observedEnd: () => RunEndReason;\n },\n): Promise<void> {\n const openRun = { ...run, agentName: auditAgentName(session.mode.get()) };\n return serializeRunAudit(session, async () => {\n const runs = await readOpenRuns(session);\n if (!runs.some(existing => existing.kickoffId === run.kickoffId)) {\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: [...runs, openRun] });\n }\n await audit.record({\n ...runAuditInput(openRun),\n actorId: run.startedBy,\n actorType,\n action: 'factory.run.started',\n });\n await recordRunEnds(session, audit, observedEnd(), run.kickoffId);\n });\n}\n\nexport function observeSessionRunEnd(session: RunEndCaptureSession, { audit }: { audit: AuditRecorder }): () => void {\n return session.subscribe(event => {\n if (event.type !== 'agent_end' || !event.reason || event.reason === 'suspended') return;\n void serializeRunAudit(session, () => recordRunEnds(session, audit, event.reason));\n });\n}\n"],"mappings":";;;AAOA,MAAa,4BAA4B;AAEzC,MAAM,uBAAuB,EAAE,OAAO;CACpC,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,OAAO;CACf,WAAW,EAAE,OAAO;CACpB,OAAO,EAAE,OAAO;CAChB,kBAAkB,EAAE,OAAO;CAC3B,YAAY,EAAE,OAAO;CACrB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,WAAW,EAAE,OAAO;CACpB,UAAU,EAAE,OAAO;CACnB,QAAQ,EAAE,OAAO;CACjB,WAAW,EAAE,OAAO;AACtB,CAAC;AAcD,MAAM,gCAAgB,IAAI,QAA6C;AAEvE,SAAS,kBAAkB,SAA+B,OAA2C;CACnG,MAAM,WAAW,cAAc,IAAI,OAAO,KAAK,QAAQ,QAAQ,EAAA,CAC5D,KAAK,KAAK,CAAC,CACX,OAAM,UAAS,QAAQ,KAAK,uDAAuD,KAAK,CAAC;CAC5F,cAAc,IAAI,SAAS,OAAO;CAClC,OAAO;AACT;AAEA,eAAe,aAAa,SAA0D;CACpF,MAAM,SAAS,MAAM,QAAQ,OAAO,WAAW,EAAE,KAAK,0BAA0B,CAAC;CACjF,OAAO,EAAE,MAAM,oBAAoB,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC;AACzD;AAEA,SAAS,cAAc,KAAqB;CAC1C,MAAM,EAAE,OAAO,kBAAkB,YAAY,cAAc,GAAG,aAAa;CAC3E,OAAO;EACL;EACA;EACA,gBAAgB,IAAI;EACpB,SAAS,CAAC;GAAE,MAAM;GAAa,IAAI;GAAY,GAAI,eAAe,EAAE,MAAM,aAAa,IAAI,CAAC;EAAG,CAAC;EAChG;CACF;AACF;AAEA,eAAe,cACb,SACA,OACA,QACA,WACe;CACf,IAAI,CAAC,UAAU,WAAW,aAAa;CACvC,MAAM,OAAO,MAAM,aAAa,OAAO;CACvC,IAAI,KAAK,WAAW,GAAG;CACvB,MAAM,YAAY,CAAC;CACnB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,aAAa,IAAI,cAAc,WAAW;GAC5C,UAAU,KAAK,GAAG;GAClB;EACF;EACA,MAAM,QAAQ,cAAc,GAAG;EAQ/B,IAAI,CAAC,MAPkB,MAAM,OAAO;GAClC,GAAG;GACH,SAAS,SAAS,IAAI;GACtB,WAAW;GACX,QAAQ;GACR,UAAU;IAAE,GAAG,MAAM;IAAU;GAAO;EACxC,CAAC,GACc,UAAU,KAAK,GAAG;CACnC;CACA,MAAM,QAAQ,OAAO,WAAW;EAAE,KAAK;EAA2B,OAAO;CAAU,CAAC;AACtF;AAEA,SAAgB,sBACd,SACA,EACE,OACA,KACA,WACA,eAOa;CACf,MAAM,UAAU;EAAE,GAAG;EAAK,WAAW,eAAe,QAAQ,KAAK,IAAI,CAAC;CAAE;CACxE,OAAO,kBAAkB,SAAS,YAAY;EAC5C,MAAM,OAAO,MAAM,aAAa,OAAO;EACvC,IAAI,CAAC,KAAK,MAAK,aAAY,SAAS,cAAc,IAAI,SAAS,GAC7D,MAAM,QAAQ,OAAO,WAAW;GAAE,KAAK;GAA2B,OAAO,CAAC,GAAG,MAAM,OAAO;EAAE,CAAC;EAE/F,MAAM,MAAM,OAAO;GACjB,GAAG,cAAc,OAAO;GACxB,SAAS,IAAI;GACb;GACA,QAAQ;EACV,CAAC;EACD,MAAM,cAAc,SAAS,OAAO,YAAY,GAAG,IAAI,SAAS;CAClE,CAAC;AACH;AAEA,SAAgB,qBAAqB,SAA+B,EAAE,SAA+C;CACnH,OAAO,QAAQ,WAAU,UAAS;EAChC,IAAI,MAAM,SAAS,eAAe,CAAC,MAAM,UAAU,MAAM,WAAW,aAAa;EACjF,kBAAuB,eAAe,cAAc,SAAS,OAAO,MAAM,MAAM,CAAC;CACnF,CAAC;AACH"}
1
+ {"version":3,"file":"run-audit.js","names":[],"sources":["../../src/session/run-audit.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\nimport { z } from 'zod';\n\nimport { auditAgentName } from '../storage/domains/audit/base.js';\nimport type { AuditActorType } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\n\nexport const FACTORY_OPEN_RUNS_SETTING = 'factoryOpenRuns';\n\nconst factoryOpenRunSchema = z.object({\n kickoffId: z.string(),\n bindingId: z.string(),\n role: z.string(),\n startedBy: z.string(),\n orgId: z.string(),\n factoryProjectId: z.string(),\n workItemId: z.string(),\n workItemName: z.string().optional(),\n sessionId: z.string(),\n threadId: z.string(),\n branch: z.string(),\n agentName: z.string(),\n /**\n * Which dispatcher process is watching this run, and when it last said so\n * (epoch ms). The ledger is a thread setting shared by every replica, so a\n * remote reader needs these to tell a run that is live elsewhere from one\n * that died with its process. Absent on entries written before ownership\n * was recorded; those read as stale.\n */\n ownerId: z.string().optional(),\n heartbeatAt: z.number().optional(),\n});\n\nexport type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;\n\n/** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */\nexport const FACTORY_OPEN_RUN_STALE_MS = 30_000;\n\nexport function isOpenRunLiveElsewhere(run: FactoryOpenRun, ownerId: string, now = Date.now()): boolean {\n return (\n run.ownerId !== undefined &&\n run.ownerId !== ownerId &&\n run.heartbeatAt !== undefined &&\n now - run.heartbeatAt < FACTORY_OPEN_RUN_STALE_MS\n );\n}\n\nexport interface RunEndCaptureSession {\n readonly thread: {\n getSetting(args: { key: string }): Promise<unknown>;\n setSetting(args: { key: string; value: unknown }): Promise<void>;\n };\n readonly mode: { get(): string };\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\nconst pendingWrites = new WeakMap<RunEndCaptureSession, Promise<void>>();\n\nfunction serializeRunAudit(session: RunEndCaptureSession, write: () => Promise<void>): Promise<void> {\n const pending = (pendingWrites.get(session) ?? Promise.resolve())\n .then(write)\n .catch(error => console.warn('[Factory run audit] Unable to record run lifecycle.', error));\n pendingWrites.set(session, pending);\n return pending;\n}\n\nexport async function listSessionOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]> {\n const stored = await session.thread.getSetting({ key: FACTORY_OPEN_RUNS_SETTING });\n return z.array(factoryOpenRunSchema).parse(stored ?? []);\n}\n\nexport async function waitForSessionRunAudit(session: RunEndCaptureSession): Promise<void> {\n await (pendingWrites.get(session) ?? Promise.resolve());\n}\n\nfunction runAuditInput(run: FactoryOpenRun) {\n // Ownership fields are dispatcher bookkeeping, not part of the run's audit record.\n const { orgId, factoryProjectId, workItemId, workItemName, ownerId, heartbeatAt, ...metadata } = run;\n void ownerId;\n void heartbeatAt;\n return {\n orgId,\n factoryProjectId,\n idempotencyKey: run.kickoffId,\n targets: [{ type: 'work_item', id: workItemId, ...(workItemName ? { name: workItemName } : {}) }],\n metadata,\n };\n}\n\nasync function recordRunEnds(\n session: RunEndCaptureSession,\n audit: AuditRecorder,\n reason: RunEndReason,\n kickoffId?: string,\n): Promise<void> {\n if (!reason || reason === 'suspended') return;\n const runs = await listSessionOpenRuns(session);\n if (runs.length === 0) return;\n const remaining = [];\n for (const run of runs) {\n if (kickoffId && run.kickoffId !== kickoffId) {\n remaining.push(run);\n continue;\n }\n const input = runAuditInput(run);\n const recorded = await audit.record({\n ...input,\n actorId: `agent:${run.threadId}`,\n actorType: 'agent',\n action: 'factory.run.ended',\n metadata: { ...input.metadata, reason },\n });\n if (!recorded) remaining.push(run);\n }\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: remaining });\n}\n\nexport function recordSessionRunStart(\n session: RunEndCaptureSession,\n {\n audit,\n run,\n actorType,\n observedEnd,\n }: {\n audit: AuditRecorder;\n run: Omit<FactoryOpenRun, 'agentName'>;\n actorType: AuditActorType;\n observedEnd: () => RunEndReason;\n },\n): Promise<void> {\n const openRun = { ...run, agentName: auditAgentName(session.mode.get()) };\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(existing => existing.kickoffId === run.kickoffId)) {\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: [...runs, openRun] });\n }\n await audit.record({\n ...runAuditInput(openRun),\n actorId: run.startedBy,\n actorType,\n action: 'factory.run.started',\n });\n await recordRunEnds(session, audit, observedEnd(), run.kickoffId);\n });\n}\n\n/** Renew this process's claim on an open run so other replicas keep treating it as live. */\nexport function heartbeatSessionOpenRun(\n session: RunEndCaptureSession,\n kickoffId: string,\n at = Date.now(),\n): Promise<void> {\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(run => run.kickoffId === kickoffId)) return;\n await session.thread.setSetting({\n key: FACTORY_OPEN_RUNS_SETTING,\n value: runs.map(run => (run.kickoffId === kickoffId ? { ...run, heartbeatAt: at } : run)),\n });\n });\n}\n\nexport function observeSessionRunEnd(session: RunEndCaptureSession, { audit }: { audit: AuditRecorder }): () => void {\n return session.subscribe(event => {\n if (event.type !== 'agent_end' || !event.reason || event.reason === 'suspended') return;\n void serializeRunAudit(session, () => recordRunEnds(session, audit, event.reason));\n });\n}\n"],"mappings":"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"}
@@ -51,6 +51,18 @@ export declare function resolveSkillInvocation(controller: Pick<AgentController<
51
51
  skillName: string;
52
52
  message: string;
53
53
  }>;
54
+ /**
55
+ * Same-stage re-entry: the skill is already active in the card's live session.
56
+ * Resolve a compact continuation that references the skill by name and carries
57
+ * only the fresh arguments, instead of re-pasting the whole skill document. If
58
+ * the skill body was compacted out of context, the agent can re-open it with the
59
+ * skill tool.
60
+ */
61
+ export declare function resolveSkillResumeInvocation(controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>, input: SkillInvocationInput): Promise<{
62
+ session: SkillSession;
63
+ skillName: string;
64
+ message: string;
65
+ }>;
54
66
  export declare function dispatchSkillInvocation(controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>, input: SkillInvocationInput, requestContext?: RequestContext): Promise<{
55
67
  skillName: string;
56
68
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/skills/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,IAAI,SAAS,CAAC;IAC1B,WAAW,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,sBAAsB,CACpB,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;QAChD,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,EACD,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;SAAE,CAAC;QAChD,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;SAAE,CAAC;QAC3C,cAAc,CAAC,EAAE,cAAc,CAAC;KACjC,GACA,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC;CACnE;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CAAC;gBAE3C,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM;CAKhE;AAMD,4EAA4E;AAC5E,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,EAC1E,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAIrD;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,EAC1E,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBxE;AAED,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,EAC1E,KAAK,EAAE,oBAAoB,EAC3B,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAIjD"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/skills/service.ts"],"names":[],"mappings":"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"}
@@ -35,6 +35,32 @@ async function resolveSkillInvocation(controller, input) {
35
35
  message: `<skill name="${skill.name}">\n${escapeSkillBoundary(content)}\n</skill>`
36
36
  };
37
37
  }
38
+ /**
39
+ * Same-stage re-entry: the skill is already active in the card's live session.
40
+ * Resolve a compact continuation that references the skill by name and carries
41
+ * only the fresh arguments, instead of re-pasting the whole skill document. If
42
+ * the skill body was compacted out of context, the agent can re-open it with the
43
+ * skill tool.
44
+ */
45
+ async function resolveSkillResumeInvocation(controller, input) {
46
+ const session = await controller.getSessionByResource(input.resourceId, input.scope);
47
+ if (!session) throw new SkillInvocationError("session_not_found", "Agent controller session not found.");
48
+ const skills = session.getWorkspace().skills;
49
+ await skills?.maybeRefresh();
50
+ const skill = await skills?.get(input.name);
51
+ if (!skill || skill["user-invocable"] === false) throw new SkillInvocationError("skill_not_found", `Skill not found: ${input.name}.`);
52
+ const args = input.arguments?.trim();
53
+ const content = [
54
+ `Resume the active ${skill.name} session — it is already open in this thread; continue it rather than restarting.`,
55
+ `Re-open the ${skill.name} skill with the skill tool if its instructions are no longer in context.`,
56
+ ...args ? [`ARGUMENTS: ${args}`] : []
57
+ ].join("\n");
58
+ return {
59
+ session,
60
+ skillName: skill.name,
61
+ message: `<skill name="${skill.name}">\n${escapeSkillBoundary(content)}\n</skill>`
62
+ };
63
+ }
38
64
  async function dispatchSkillInvocation(controller, input, requestContext) {
39
65
  const resolved = await resolveSkillInvocation(controller, input);
40
66
  await resolved.session.sendMessage({
@@ -47,6 +73,6 @@ async function dispatchSkillInvocation(controller, input, requestContext) {
47
73
  };
48
74
  }
49
75
  //#endregion
50
- export { SkillInvocationError, dispatchSkillInvocation, resolvePromptInvocation, resolveSkillInvocation };
76
+ export { SkillInvocationError, dispatchSkillInvocation, resolvePromptInvocation, resolveSkillInvocation, resolveSkillResumeInvocation };
51
77
 
52
78
  //# sourceMappingURL=service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../src/skills/service.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { SendAgentSignalResult } from '@mastra/core/agent';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\nimport type { Workspace } from '@mastra/core/workspace';\n\nexport interface SkillInvocationInput {\n resourceId: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\nexport interface SkillSession {\n getWorkspace(): Workspace;\n sendMessage(input: { content: string; requestContext?: RequestContext }): Promise<unknown>;\n sendNotificationSignal(\n input: {\n source: string;\n kind: string;\n summary: string;\n priority?: 'low' | 'medium' | 'high' | 'urgent';\n payload?: unknown;\n dedupeKey?: string;\n sourceId?: string;\n },\n options?: {\n ifActive?: { behavior?: 'deliver' | 'persist' };\n ifIdle?: { behavior?: 'persist' | 'wake' };\n requestContext?: RequestContext;\n },\n ): Promise<Pick<SendAgentSignalResult, 'persisted' | 'accepted'>>;\n}\n\nexport class SkillInvocationError extends Error {\n readonly code: 'session_not_found' | 'skill_not_found';\n\n constructor(code: SkillInvocationError['code'], message: string) {\n super(message);\n this.name = 'SkillInvocationError';\n this.code = code;\n }\n}\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '&lt;/skill&gt;');\n}\n\n/** Kicks a run off from a plain prompt, for runs that activate no skill. */\nexport async function resolvePromptInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: { resourceId: string; scope?: SkillInvocationInput['scope']; prompt: string },\n): Promise<{ session: SkillSession; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n return { session, message: input.prompt };\n}\n\nexport async function resolveSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\nexport async function dispatchSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n requestContext?: RequestContext,\n): Promise<{ skillName: string; message: string }> {\n const resolved = await resolveSkillInvocation(controller, input);\n await resolved.session.sendMessage({ content: resolved.message, ...(requestContext ? { requestContext } : {}) });\n return { skillName: resolved.skillName, message: resolved.message };\n}\n"],"mappings":";;AAmCA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAoC,SAAiB;EAC/D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,SAAS,oBAAoB,OAAuB;CAClD,OAAO,MAAM,WAAW,YAAY,gBAAgB;AACtD;;AAGA,eAAsB,wBACpB,YACA,OACqD;CACrD,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CACvG,OAAO;EAAE;EAAS,SAAS,MAAM;CAAO;AAC1C;AAEA,eAAsB,uBACpB,YACA,OACwE;CACxE,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,MAAM,QAAQ,aAAa;CAC3B,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI;CAC1C,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,UAAU,GAAG,sBAAsB,KAAK,IAAI,OAAO,kBAAkB,SAAS,KAAK,KAAK;CAC9F,OAAO;EACL;EACA,WAAW,MAAM;EACjB,SAAS,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;CACzE;AACF;AAEA,eAAsB,wBACpB,YACA,OACA,gBACiD;CACjD,MAAM,WAAW,MAAM,uBAAuB,YAAY,KAAK;CAC/D,MAAM,SAAS,QAAQ,YAAY;EAAE,SAAS,SAAS;EAAS,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;CAAG,CAAC;CAC/G,OAAO;EAAE,WAAW,SAAS;EAAW,SAAS,SAAS;CAAQ;AACpE"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../src/skills/service.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { SendAgentSignalResult } from '@mastra/core/agent';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\nimport type { Workspace } from '@mastra/core/workspace';\n\nexport interface SkillInvocationInput {\n resourceId: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\nexport interface SkillSession {\n getWorkspace(): Workspace;\n sendMessage(input: { content: string; requestContext?: RequestContext }): Promise<unknown>;\n sendNotificationSignal(\n input: {\n source: string;\n kind: string;\n summary: string;\n priority?: 'low' | 'medium' | 'high' | 'urgent';\n payload?: unknown;\n dedupeKey?: string;\n sourceId?: string;\n },\n options?: {\n ifActive?: { behavior?: 'deliver' | 'persist' };\n ifIdle?: { behavior?: 'persist' | 'wake' };\n requestContext?: RequestContext;\n },\n ): Promise<Pick<SendAgentSignalResult, 'persisted' | 'accepted'>>;\n}\n\nexport class SkillInvocationError extends Error {\n readonly code: 'session_not_found' | 'skill_not_found';\n\n constructor(code: SkillInvocationError['code'], message: string) {\n super(message);\n this.name = 'SkillInvocationError';\n this.code = code;\n }\n}\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '&lt;/skill&gt;');\n}\n\n/** Kicks a run off from a plain prompt, for runs that activate no skill. */\nexport async function resolvePromptInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: { resourceId: string; scope?: SkillInvocationInput['scope']; prompt: string },\n): Promise<{ session: SkillSession; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n return { session, message: input.prompt };\n}\n\nexport async function resolveSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\n/**\n * Same-stage re-entry: the skill is already active in the card's live session.\n * Resolve a compact continuation that references the skill by name and carries\n * only the fresh arguments, instead of re-pasting the whole skill document. If\n * the skill body was compacted out of context, the agent can re-open it with the\n * skill tool.\n */\nexport async function resolveSkillResumeInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = [\n `Resume the active ${skill.name} session — it is already open in this thread; continue it rather than restarting.`,\n `Re-open the ${skill.name} skill with the skill tool if its instructions are no longer in context.`,\n ...(args ? [`ARGUMENTS: ${args}`] : []),\n ].join('\\n');\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\nexport async function dispatchSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n requestContext?: RequestContext,\n): Promise<{ skillName: string; message: string }> {\n const resolved = await resolveSkillInvocation(controller, input);\n await resolved.session.sendMessage({ content: resolved.message, ...(requestContext ? { requestContext } : {}) });\n return { skillName: resolved.skillName, message: resolved.message };\n}\n"],"mappings":";;AAmCA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAoC,SAAiB;EAC/D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,SAAS,oBAAoB,OAAuB;CAClD,OAAO,MAAM,WAAW,YAAY,gBAAgB;AACtD;;AAGA,eAAsB,wBACpB,YACA,OACqD;CACrD,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CACvG,OAAO;EAAE;EAAS,SAAS,MAAM;CAAO;AAC1C;AAEA,eAAsB,uBACpB,YACA,OACwE;CACxE,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,MAAM,QAAQ,aAAa;CAC3B,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI;CAC1C,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,UAAU,GAAG,sBAAsB,KAAK,IAAI,OAAO,kBAAkB,SAAS,KAAK,KAAK;CAC9F,OAAO;EACL;EACA,WAAW,MAAM;EACjB,SAAS,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;CACzE;AACF;;;;;;;;AASA,eAAsB,6BACpB,YACA,OACwE;CACxE,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,MAAM,QAAQ,aAAa;CAC3B,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI;CAC1C,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,UAAU;EACd,qBAAqB,MAAM,KAAK;EAChC,eAAe,MAAM,KAAK;EAC1B,GAAI,OAAO,CAAC,cAAc,MAAM,IAAI,CAAC;CACvC,CAAC,CAAC,KAAK,IAAI;CACX,OAAO;EACL;EACA,WAAW,MAAM;EACjB,SAAS,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;CACzE;AACF;AAEA,eAAsB,wBACpB,YACA,OACA,gBACiD;CACjD,MAAM,WAAW,MAAM,uBAAuB,YAAY,KAAK;CAC/D,MAAM,SAAS,QAAQ,YAAY;EAAE,SAAS,SAAS;EAAS,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;CAAG,CAAC;CAC/G,OAAO;EAAE,WAAW,SAAS;EAAW,SAAS,SAAS;CAAQ;AACpE"}
@@ -123,8 +123,8 @@ export interface FactoryRuleEvaluationRecord {
123
123
  }
124
124
  /** `proposed` is parked awaiting approval; `dismissed` is human, `superseded` is automatic. */
125
125
  export type FactoryDispatchStatus = 'pending' | 'proposed' | 'dismissed' | 'superseded' | 'leased' | 'retry' | 'succeeded' | 'failed';
126
- declare const FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown"];
127
- declare const STORED_FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown", "plan_awaiting_approval", "run_awaiting_input"];
126
+ declare const FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_terminal_event_missing", "skill_delivery_ambiguous", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown"];
127
+ declare const STORED_FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_terminal_event_missing", "skill_delivery_ambiguous", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown", "plan_awaiting_approval", "run_awaiting_input"];
128
128
  export type FactoryDispatchFailureCode = (typeof FACTORY_DISPATCH_FAILURE_CODES)[number];
129
129
  export type StoredFactoryDispatchFailureCode = (typeof STORED_FACTORY_DISPATCH_FAILURE_CODES)[number];
130
130
  export interface FactoryDeferredDecisionPageInput {
@@ -386,6 +386,12 @@ export interface WorkItemRow {
386
386
  factoryProjectId: string;
387
387
  board: string | null;
388
388
  externalSource: ExternalWorkItemSource | null;
389
+ /**
390
+ * Org-wide ownership key for the external record this card tracks, or null
391
+ * for cards that never claimed one (older rows, integrations without claims,
392
+ * finished cards). Unique per org while set.
393
+ */
394
+ claimKey: string | null;
389
395
  parentWorkItemId: string | null;
390
396
  title: string;
391
397
  stages: WorkItemStage[];
@@ -424,6 +430,8 @@ export interface WorkItemRow {
424
430
  export interface CreateWorkItemInput {
425
431
  board?: string;
426
432
  externalSource?: ExternalWorkItemSource | null;
433
+ /** See {@link WorkItemRow.claimKey}. Refused with {@link WorkItemClaimConflictError} when another project holds it. */
434
+ claimKey?: string | null;
427
435
  parentWorkItemId?: string | null;
428
436
  title: string;
429
437
  stages?: WorkItemStage[];
@@ -459,6 +467,12 @@ export declare function externalSourceKey(source: ExternalWorkItemSource | null
459
467
  export declare class WorkItemRelationError extends Error {
460
468
  readonly code = "invalid_work_item_relation";
461
469
  }
470
+ /** Another card in the org already holds the claim the write asked for. */
471
+ export declare class WorkItemClaimConflictError extends Error {
472
+ readonly claimant: WorkItemRow;
473
+ readonly code = "work_item_claim_conflict";
474
+ constructor(claimant: WorkItemRow);
475
+ }
462
476
  export declare class WorkItemUpdateConflictError extends Error {
463
477
  readonly reason: 'board' | 'revision';
464
478
  readonly code = "work_item_update_conflict";
@@ -555,6 +569,31 @@ export declare class WorkItemsStorage extends FactoryStorageDomain {
555
569
  * `workspaceId` instead. Ambiguous matches resolve to nothing, not a guess.
556
570
  */
557
571
  getBySource(source: ExternalWorkItemSource): Promise<WorkItemRow | null>;
572
+ /**
573
+ * Every card in the org that was linked from one external source, across
574
+ * Factory projects. Source keys are unique per project, not per org, so an
575
+ * issue whose routing moved between projects can own one card in each; intake
576
+ * consults this before creating another.
577
+ */
578
+ listBySource({ orgId, source }: {
579
+ orgId: string;
580
+ source: ExternalWorkItemSource;
581
+ }): Promise<WorkItemRow[]>;
582
+ /** The one card in the org holding `claimKey`, if any. */
583
+ getByClaimKey({ orgId, claimKey }: {
584
+ orgId: string;
585
+ claimKey: string;
586
+ }): Promise<WorkItemRow | null>;
587
+ /**
588
+ * Give an unclaimed card the org-wide claim for its record. Returns the card
589
+ * once claimed, or null when the card already carries a different claim or
590
+ * another card in the org holds this one.
591
+ */
592
+ claimWorkItem({ orgId, id, claimKey, }: {
593
+ orgId: string;
594
+ id: string;
595
+ claimKey: string;
596
+ }): Promise<WorkItemRow | null>;
558
597
  getForProject(orgId: string, factoryProjectId: string, id: string): Promise<WorkItemRow | null>;
559
598
  getTransitionResultByIngress(orgId: string, factoryProjectId: string, identity: string): Promise<Record<string, unknown> | null>;
560
599
  commitTransition(input: CommitFactoryTransitionInput): Promise<CommitFactoryTransitionResult>;