@lota-sdk/core 0.4.7 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,818 +0,0 @@
1
- import type {
2
- PlanCompletionCheck,
3
- PlanDataSchema,
4
- PlanDraft,
5
- PlanFailureClass,
6
- PlanNodeType,
7
- PlanNodeResultSubmission,
8
- PlanValidationIssueSeverity,
9
- } from '@lota-sdk/shared'
10
- import { HUMAN_NODE_TYPES, STRUCTURAL_NODE_TYPES } from '@lota-sdk/shared'
11
-
12
- import { isRecord } from '../utils/string'
13
- import { planCoordinationService } from './plan-coordination.service'
14
- import { readPathValue } from './plan-helpers'
15
- import type { PlanNodeValidationSpec } from './plan-node-spec'
16
-
17
- const STRUCTURAL_NODE_TYPE_SET = new Set<PlanNodeType>(STRUCTURAL_NODE_TYPES)
18
- const HUMAN_NODE_TYPE_SET = new Set<PlanNodeType>(HUMAN_NODE_TYPES)
19
-
20
- export interface PlanValidationIssueInput {
21
- severity: PlanValidationIssueSeverity
22
- code: string
23
- message: string
24
- detail?: Record<string, unknown>
25
- nodeId?: string
26
- }
27
-
28
- interface DraftValidationResult {
29
- blocking: PlanValidationIssueInput[]
30
- warnings: PlanValidationIssueInput[]
31
- }
32
-
33
- interface NodeResultValidationResult {
34
- blocking: PlanValidationIssueInput[]
35
- warnings: PlanValidationIssueInput[]
36
- failureClass: PlanFailureClass | null
37
- }
38
-
39
- function createIssue(params: {
40
- code: string
41
- message: string
42
- severity?: PlanValidationIssueSeverity
43
- detail?: Record<string, unknown>
44
- nodeId?: string
45
- }): PlanValidationIssueInput {
46
- return {
47
- severity: params.severity ?? 'blocking',
48
- code: params.code,
49
- message: params.message,
50
- ...(params.detail ? { detail: params.detail } : {}),
51
- ...(params.nodeId ? { nodeId: params.nodeId } : {}),
52
- }
53
- }
54
-
55
- function hasAllFields(value: unknown, fields: string[]): boolean {
56
- if (!isRecord(value)) return false
57
- return fields.every((field) => readPathValue(value, field) !== undefined)
58
- }
59
-
60
- export function validateSchemaValue(params: { schema: PlanDataSchema; value: unknown; path: string }): string[] {
61
- const issues: string[] = []
62
- const { schema, value, path } = params
63
-
64
- if (value === null || value === undefined) {
65
- if (schema.required || (!schema.nullable && value === null)) {
66
- issues.push(`${path} is required`)
67
- }
68
- return issues
69
- }
70
-
71
- if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
72
- issues.push(`${path} must be one of the allowed enum values`)
73
- return issues
74
- }
75
-
76
- if (schema.type === 'string') {
77
- if (typeof value !== 'string') issues.push(`${path} must be a string`)
78
- return issues
79
- }
80
-
81
- if (schema.type === 'number') {
82
- if (typeof value !== 'number' || Number.isNaN(value)) issues.push(`${path} must be a number`)
83
- return issues
84
- }
85
-
86
- if (schema.type === 'boolean') {
87
- if (typeof value !== 'boolean') issues.push(`${path} must be a boolean`)
88
- return issues
89
- }
90
-
91
- if (schema.type === 'array') {
92
- if (!Array.isArray(value)) {
93
- issues.push(`${path} must be an array`)
94
- return issues
95
- }
96
- if (schema.minItems !== undefined && value.length < schema.minItems) {
97
- issues.push(`${path} must contain at least ${schema.minItems} item(s)`)
98
- }
99
- if (schema.maxItems !== undefined && value.length > schema.maxItems) {
100
- issues.push(`${path} must contain at most ${schema.maxItems} item(s)`)
101
- }
102
- const itemSchema = schema.items
103
- if (itemSchema) {
104
- value.forEach((entry, index) => {
105
- issues.push(...validateSchemaValue({ schema: itemSchema, value: entry, path: `${path}[${index}]` }))
106
- })
107
- }
108
- return issues
109
- }
110
-
111
- if (!isRecord(value)) {
112
- issues.push(`${path} must be an object`)
113
- return issues
114
- }
115
-
116
- for (const [key, childSchema] of Object.entries(schema.properties ?? {})) {
117
- issues.push(...validateSchemaValue({ schema: childSchema, value: value[key], path: `${path}.${key}` }))
118
- }
119
-
120
- return issues
121
- }
122
-
123
- function buildReachableNodeIds(entryNodeIds: string[], adjacency: Map<string, string[]>): Set<string> {
124
- const visited = new Set<string>()
125
- const queue = [...entryNodeIds]
126
-
127
- while (queue.length > 0) {
128
- const nodeId = queue.shift()
129
- if (!nodeId || visited.has(nodeId)) continue
130
- visited.add(nodeId)
131
- for (const next of adjacency.get(nodeId) ?? []) {
132
- if (!visited.has(next)) {
133
- queue.push(next)
134
- }
135
- }
136
- }
137
-
138
- return visited
139
- }
140
-
141
- function graphHasCycle(nodeIds: string[], adjacency: Map<string, string[]>): boolean {
142
- const visiting = new Set<string>()
143
- const visited = new Set<string>()
144
-
145
- const visit = (nodeId: string): boolean => {
146
- if (visited.has(nodeId)) return false
147
- if (visiting.has(nodeId)) return true
148
-
149
- visiting.add(nodeId)
150
- for (const next of adjacency.get(nodeId) ?? []) {
151
- if (visit(next)) return true
152
- }
153
- visiting.delete(nodeId)
154
- visited.add(nodeId)
155
- return false
156
- }
157
-
158
- return nodeIds.some((nodeId) => visit(nodeId))
159
- }
160
-
161
- function resolveSchemaRef(
162
- draft: Pick<PlanDraft, 'schemas'>,
163
- schemaRef: string | null | undefined,
164
- ): PlanDataSchema | undefined {
165
- if (!schemaRef) return undefined
166
- return draft.schemas[schemaRef]
167
- }
168
-
169
- class PlanValidatorService {
170
- validateDraft(draft: PlanDraft): DraftValidationResult {
171
- const blocking: PlanValidationIssueInput[] = []
172
- const warnings: PlanValidationIssueInput[] = []
173
- const nodeIds = new Set<string>()
174
- const edgeIds = new Set<string>()
175
- const nodesById = new Map(draft.nodes.map((node) => [node.id, node]))
176
- const adjacency = new Map<string, string[]>(draft.nodes.map((node) => [node.id, []]))
177
- const inbound = new Map<string, string[]>(draft.nodes.map((node) => [node.id, []]))
178
-
179
- for (const node of draft.nodes) {
180
- if (nodeIds.has(node.id)) {
181
- blocking.push(
182
- createIssue({
183
- code: 'duplicate_node_id',
184
- message: `Plan contains duplicate node id "${node.id}".`,
185
- nodeId: node.id,
186
- }),
187
- )
188
- }
189
- nodeIds.add(node.id)
190
-
191
- const isStructuralNode = STRUCTURAL_NODE_TYPE_SET.has(node.type)
192
- if (!isStructuralNode && node.deliverables.length === 0) {
193
- blocking.push(
194
- createIssue({
195
- code: 'node_missing_deliverables',
196
- message: `Node "${node.label}" must define at least one deliverable.`,
197
- nodeId: node.id,
198
- }),
199
- )
200
- }
201
- if (node.successCriteria.length === 0) {
202
- blocking.push(
203
- createIssue({
204
- code: 'node_missing_success_criteria',
205
- message: `Node "${node.label}" must define at least one success criterion.`,
206
- nodeId: node.id,
207
- }),
208
- )
209
- }
210
- if (!isStructuralNode && node.completionChecks.length === 0) {
211
- blocking.push(
212
- createIssue({
213
- code: 'node_missing_completion_checks',
214
- message: `Node "${node.label}" must define at least one completion check.`,
215
- nodeId: node.id,
216
- }),
217
- )
218
- }
219
- if (HUMAN_NODE_TYPE_SET.has(node.type) && node.owner.executorType !== 'user') {
220
- blocking.push(
221
- createIssue({
222
- code: 'human_node_owner_mismatch',
223
- message: `Human node "${node.label}" must be owned by a user executor.`,
224
- nodeId: node.id,
225
- }),
226
- )
227
- }
228
- if (
229
- !HUMAN_NODE_TYPE_SET.has(node.type) &&
230
- node.type !== 'join' &&
231
- node.type !== 'switch' &&
232
- node.owner.ref.trim().length === 0
233
- ) {
234
- blocking.push(
235
- createIssue({
236
- code: 'node_owner_missing',
237
- message: `Node "${node.label}" must declare an executor reference.`,
238
- nodeId: node.id,
239
- }),
240
- )
241
- }
242
- if (node.owner.executorType === 'plugin' && node.owner.operation.trim().length === 0) {
243
- blocking.push(
244
- createIssue({
245
- code: 'plugin_owner_operation_missing',
246
- message: `Plugin-owned node "${node.label}" must declare an operation.`,
247
- nodeId: node.id,
248
- }),
249
- )
250
- }
251
- if (node.owner.executorType === 'system' && node.owner.operation.trim().length === 0) {
252
- blocking.push(
253
- createIssue({
254
- code: 'system_owner_operation_missing',
255
- message: `System-owned node "${node.label}" must declare an operation.`,
256
- nodeId: node.id,
257
- }),
258
- )
259
- }
260
- if (node.inputSchemaRef && !resolveSchemaRef(draft, node.inputSchemaRef)) {
261
- blocking.push(
262
- createIssue({
263
- code: 'missing_input_schema_ref',
264
- message: `Node "${node.label}" references unknown input schema "${node.inputSchemaRef}".`,
265
- nodeId: node.id,
266
- }),
267
- )
268
- }
269
- if (node.outputSchemaRef && !resolveSchemaRef(draft, node.outputSchemaRef)) {
270
- blocking.push(
271
- createIssue({
272
- code: 'missing_output_schema_ref',
273
- message: `Node "${node.label}" references unknown output schema "${node.outputSchemaRef}".`,
274
- nodeId: node.id,
275
- }),
276
- )
277
- }
278
- for (const deliverable of node.deliverables) {
279
- if (deliverable.schemaRef && !resolveSchemaRef(draft, deliverable.schemaRef)) {
280
- blocking.push(
281
- createIssue({
282
- code: 'missing_artifact_schema_ref',
283
- message: `Node "${node.label}" references unknown artifact schema "${deliverable.schemaRef}".`,
284
- nodeId: node.id,
285
- detail: { artifactName: deliverable.name },
286
- }),
287
- )
288
- }
289
- }
290
- for (const check of node.completionChecks) {
291
- const checkSchemaRef = typeof check.config.schemaRef === 'string' ? check.config.schemaRef : undefined
292
- if (checkSchemaRef && !resolveSchemaRef(draft, checkSchemaRef)) {
293
- blocking.push(
294
- createIssue({
295
- code: 'missing_completion_check_schema_ref',
296
- message: `Node "${node.label}" references unknown completion-check schema "${checkSchemaRef}".`,
297
- nodeId: node.id,
298
- detail: { checkDescription: check.description },
299
- }),
300
- )
301
- }
302
- }
303
- if (node.toolPolicy.allow.some((toolName) => node.toolPolicy.deny.includes(toolName))) {
304
- warnings.push(
305
- createIssue({
306
- code: 'tool_policy_overlap',
307
- severity: 'warning',
308
- message: `Node "${node.label}" lists the same tool in allow and deny policy.`,
309
- nodeId: node.id,
310
- }),
311
- )
312
- }
313
-
314
- // Validate deliberation-fork nodes
315
- if (node.type === 'deliberation-fork') {
316
- if (!node.deliberationConfig) {
317
- blocking.push(
318
- createIssue({
319
- code: 'deliberation_fork_missing_config',
320
- message: `Deliberation-fork node "${node.label}" must define deliberationConfig.`,
321
- nodeId: node.id,
322
- }),
323
- )
324
- } else {
325
- if (node.deliberationConfig.branches.length < 2) {
326
- blocking.push(
327
- createIssue({
328
- code: 'deliberation_fork_insufficient_branches',
329
- message: `Deliberation-fork node "${node.label}" must define at least 2 branches.`,
330
- nodeId: node.id,
331
- }),
332
- )
333
- }
334
- for (const branch of node.deliberationConfig.branches) {
335
- if (!nodesById.has(branch.entryNodeId)) {
336
- blocking.push(
337
- createIssue({
338
- code: 'deliberation_fork_missing_branch_entry',
339
- message: `Deliberation-fork node "${node.label}" references missing branch entry node "${branch.entryNodeId}".`,
340
- nodeId: node.id,
341
- detail: { branchId: branch.branchId, entryNodeId: branch.entryNodeId },
342
- }),
343
- )
344
- }
345
- }
346
- const gateNode = nodesById.get(node.deliberationConfig.resolutionGateNodeId)
347
- if (!gateNode) {
348
- blocking.push(
349
- createIssue({
350
- code: 'deliberation_fork_missing_gate',
351
- message: `Deliberation-fork node "${node.label}" references missing resolution gate node "${node.deliberationConfig.resolutionGateNodeId}".`,
352
- nodeId: node.id,
353
- detail: { resolutionGateNodeId: node.deliberationConfig.resolutionGateNodeId },
354
- }),
355
- )
356
- } else if (gateNode.type !== 'human-decision') {
357
- blocking.push(
358
- createIssue({
359
- code: 'deliberation_fork_gate_type_mismatch',
360
- message: `Resolution gate node "${gateNode.label}" must be of type "human-decision".`,
361
- nodeId: node.id,
362
- detail: { gateNodeId: gateNode.id, gateNodeType: gateNode.type },
363
- }),
364
- )
365
- }
366
- }
367
- }
368
- }
369
-
370
- const entryNodeIds = draft.entryNodeIds ?? []
371
- for (const entryNodeId of entryNodeIds) {
372
- if (!nodesById.has(entryNodeId)) {
373
- blocking.push(
374
- createIssue({
375
- code: 'missing_entry_node',
376
- message: `Entry node "${entryNodeId}" does not exist in the plan.`,
377
- detail: { entryNodeId },
378
- }),
379
- )
380
- }
381
- }
382
-
383
- for (const edge of draft.edges) {
384
- if (edgeIds.has(edge.id)) {
385
- blocking.push(
386
- createIssue({
387
- code: 'duplicate_edge_id',
388
- message: `Plan contains duplicate edge id "${edge.id}".`,
389
- detail: { edgeId: edge.id },
390
- }),
391
- )
392
- }
393
- edgeIds.add(edge.id)
394
-
395
- if (!nodesById.has(edge.source)) {
396
- blocking.push(
397
- createIssue({
398
- code: 'missing_edge_source',
399
- message: `Edge "${edge.id}" references missing source node "${edge.source}".`,
400
- detail: { edgeId: edge.id, source: edge.source },
401
- }),
402
- )
403
- continue
404
- }
405
- if (!nodesById.has(edge.target)) {
406
- blocking.push(
407
- createIssue({
408
- code: 'missing_edge_target',
409
- message: `Edge "${edge.id}" references missing target node "${edge.target}".`,
410
- detail: { edgeId: edge.id, target: edge.target },
411
- }),
412
- )
413
- continue
414
- }
415
-
416
- adjacency.get(edge.source)?.push(edge.target)
417
- inbound.get(edge.target)?.push(edge.source)
418
- }
419
-
420
- for (const node of draft.nodes) {
421
- const inboundEdges = inbound.get(node.id) ?? []
422
- const outboundEdges = adjacency.get(node.id) ?? []
423
-
424
- if (node.type === 'join' && inboundEdges.length < 2) {
425
- blocking.push(
426
- createIssue({
427
- code: 'join_requires_multiple_inbound_edges',
428
- message: `Join node "${node.label}" must have at least two inbound edges.`,
429
- nodeId: node.id,
430
- }),
431
- )
432
- }
433
-
434
- if (node.type === 'switch') {
435
- const conditionalEdges = draft.edges.filter((edge) => edge.source === node.id && edge.when)
436
- if (conditionalEdges.length === 0) {
437
- blocking.push(
438
- createIssue({
439
- code: 'switch_requires_conditional_edges',
440
- message: `Switch node "${node.label}" must define conditional outbound edges.`,
441
- nodeId: node.id,
442
- }),
443
- )
444
- }
445
- if (outboundEdges.length < 2) {
446
- warnings.push(
447
- createIssue({
448
- code: 'switch_has_single_path',
449
- severity: 'warning',
450
- message: `Switch node "${node.label}" only routes to one target.`,
451
- nodeId: node.id,
452
- }),
453
- )
454
- }
455
- }
456
-
457
- if (!entryNodeIds.includes(node.id) && inboundEdges.length === 0) {
458
- blocking.push(
459
- createIssue({
460
- code: 'orphan_node',
461
- message: `Node "${node.label}" is not connected to the graph.`,
462
- nodeId: node.id,
463
- }),
464
- )
465
- }
466
-
467
- if (node.inputSchemaRef && inboundEdges.length > 0) {
468
- const hasMappedInput = draft.edges
469
- .filter((edge) => edge.target === node.id)
470
- .some((edge) => Object.keys(edge.map).length > 0)
471
- if (!hasMappedInput) {
472
- blocking.push(
473
- createIssue({
474
- code: 'unresolved_node_input',
475
- message: `Node "${node.label}" declares inputSchemaRef but no inbound edge maps data into it.`,
476
- nodeId: node.id,
477
- }),
478
- )
479
- }
480
- }
481
- }
482
-
483
- const reachableNodeIds = buildReachableNodeIds(entryNodeIds, adjacency)
484
- for (const node of draft.nodes) {
485
- if (!reachableNodeIds.has(node.id)) {
486
- blocking.push(
487
- createIssue({
488
- code: 'unreachable_node',
489
- message: `Node "${node.label}" is unreachable from the configured entry nodes.`,
490
- nodeId: node.id,
491
- }),
492
- )
493
- }
494
- }
495
-
496
- if (
497
- graphHasCycle(
498
- draft.nodes.map((node) => node.id),
499
- adjacency,
500
- )
501
- ) {
502
- blocking.push(
503
- createIssue({
504
- code: 'cycle_detected',
505
- message: 'Plan graph contains a cycle. Loop semantics are not supported in this cutover.',
506
- }),
507
- )
508
- }
509
-
510
- // Validate cross-plan dependency cycles
511
- if (draft.dependencies && draft.dependencies.length > 0) {
512
- const cycleIssues = planCoordinationService.validateNoCycles([
513
- { id: draft.title, title: draft.title, dependencies: draft.dependencies },
514
- ])
515
- blocking.push(...cycleIssues)
516
- }
517
-
518
- return { blocking, warnings }
519
- }
520
-
521
- validateNodeResult(params: {
522
- draft: Pick<PlanDraft, 'schemas'>
523
- node: PlanNodeValidationSpec
524
- result: PlanNodeResultSubmission
525
- }): NodeResultValidationResult {
526
- const blocking: PlanValidationIssueInput[] = []
527
- const warnings: PlanValidationIssueInput[] = []
528
- const artifactsByName = new Map(params.result.artifacts.map((artifact) => [artifact.name, artifact]))
529
-
530
- if (params.node.outputSchemaRef) {
531
- const outputSchema = resolveSchemaRef(params.draft, params.node.outputSchemaRef)
532
- if (!params.result.structuredOutput) {
533
- blocking.push(
534
- createIssue({
535
- code: 'structured_output_missing',
536
- message: `Node "${params.node.label}" requires structured output.`,
537
- nodeId: params.node.id,
538
- }),
539
- )
540
- } else if (outputSchema) {
541
- const schemaIssues = validateSchemaValue({
542
- schema: outputSchema,
543
- value: params.result.structuredOutput,
544
- path: 'structuredOutput',
545
- })
546
- if (schemaIssues.length > 0) {
547
- blocking.push(
548
- createIssue({
549
- code: 'schema_validation_failed',
550
- message: `Structured output for node "${params.node.label}" failed schema validation.`,
551
- nodeId: params.node.id,
552
- detail: { issues: schemaIssues },
553
- }),
554
- )
555
- }
556
- }
557
- }
558
-
559
- for (const deliverable of params.node.deliverables) {
560
- const artifact = artifactsByName.get(deliverable.name)
561
- if (!artifact) {
562
- if (deliverable.required) {
563
- blocking.push(
564
- createIssue({
565
- code: 'required_artifact_missing',
566
- message: `Node "${params.node.label}" is missing required artifact "${deliverable.name}".`,
567
- nodeId: params.node.id,
568
- }),
569
- )
570
- }
571
- continue
572
- }
573
-
574
- if (artifact.kind !== deliverable.kind) {
575
- blocking.push(
576
- createIssue({
577
- code: 'artifact_kind_mismatch',
578
- message: `Artifact "${deliverable.name}" must be of kind "${deliverable.kind}".`,
579
- nodeId: params.node.id,
580
- }),
581
- )
582
- }
583
-
584
- if (deliverable.schemaRef) {
585
- const artifactSchema = resolveSchemaRef(params.draft, deliverable.schemaRef)
586
- if (!artifact.payload) {
587
- blocking.push(
588
- createIssue({
589
- code: 'artifact_payload_missing',
590
- message: `Artifact "${deliverable.name}" must include payload data for schema validation.`,
591
- nodeId: params.node.id,
592
- }),
593
- )
594
- } else if (artifactSchema) {
595
- const schemaIssues = validateSchemaValue({
596
- schema: artifactSchema,
597
- value: artifact.payload,
598
- path: `artifact:${deliverable.name}`,
599
- })
600
- if (schemaIssues.length > 0) {
601
- blocking.push(
602
- createIssue({
603
- code: 'schema_validation_failed',
604
- message: `Artifact "${deliverable.name}" failed schema validation.`,
605
- nodeId: params.node.id,
606
- detail: { issues: schemaIssues, artifact: deliverable.name },
607
- }),
608
- )
609
- }
610
- }
611
- }
612
- }
613
-
614
- for (const check of params.node.completionChecks) {
615
- const issue = this.evaluateCompletionCheck({
616
- draft: params.draft,
617
- node: params.node,
618
- result: params.result,
619
- check,
620
- })
621
- if (!issue) continue
622
- if (issue.severity === 'warning') {
623
- warnings.push(issue)
624
- } else {
625
- blocking.push(issue)
626
- }
627
- }
628
-
629
- return { blocking, warnings, failureClass: this.resolveFailureClass(blocking) }
630
- }
631
-
632
- private evaluateCompletionCheck(params: {
633
- draft: Pick<PlanDraft, 'schemas'>
634
- node: PlanNodeValidationSpec
635
- result: PlanNodeResultSubmission
636
- check: PlanCompletionCheck
637
- }): PlanValidationIssueInput | null {
638
- const { check, node, result, draft } = params
639
- const artifactName = typeof check.config.artifact === 'string' ? check.config.artifact : undefined
640
- const artifact = artifactName ? result.artifacts.find((candidate) => candidate.name === artifactName) : undefined
641
- const target =
642
- artifactName && artifact
643
- ? artifact.payload
644
- : typeof check.config.target === 'string'
645
- ? readPathValue(result.structuredOutput, check.config.target)
646
- : result.structuredOutput
647
-
648
- if (check.type === 'schema') {
649
- const schemaRef =
650
- (typeof check.config.schemaRef === 'string' ? check.config.schemaRef : undefined) ??
651
- (artifactName ? node.deliverables.find((candidate) => candidate.name === artifactName)?.schemaRef : undefined)
652
- if (!schemaRef) {
653
- return createIssue({
654
- code: 'schema_check_missing_schema_ref',
655
- message: `Completion check "${check.description}" does not resolve a schema reference.`,
656
- nodeId: node.id,
657
- severity: check.blocking ? 'blocking' : 'warning',
658
- })
659
- }
660
-
661
- if (artifactName && !artifact) {
662
- return createIssue({
663
- code: 'schema_check_artifact_missing',
664
- message: `Completion check "${check.description}" requires artifact "${artifactName}".`,
665
- nodeId: node.id,
666
- severity: check.blocking ? 'blocking' : 'warning',
667
- })
668
- }
669
-
670
- const schema = resolveSchemaRef(draft, schemaRef)
671
- if (!schema) {
672
- return createIssue({
673
- code: 'schema_check_schema_missing',
674
- message: `Completion check "${check.description}" references unknown schema "${schemaRef}".`,
675
- nodeId: node.id,
676
- severity: check.blocking ? 'blocking' : 'warning',
677
- })
678
- }
679
-
680
- const schemaIssues = validateSchemaValue({
681
- schema,
682
- value: target,
683
- path: artifactName ? `artifact:${artifactName}` : 'structuredOutput',
684
- })
685
- if (schemaIssues.length > 0) {
686
- return createIssue({
687
- code: 'schema_validation_failed',
688
- message: `Completion check "${check.description}" failed schema validation.`,
689
- nodeId: node.id,
690
- severity: check.blocking ? 'blocking' : 'warning',
691
- detail: { issues: schemaIssues, schemaRef },
692
- })
693
- }
694
-
695
- return null
696
- }
697
-
698
- if (check.type === 'assertion') {
699
- const requiredFields = Array.isArray(check.config.mustContainFields)
700
- ? check.config.mustContainFields.filter((field): field is string => typeof field === 'string')
701
- : []
702
- if (requiredFields.length > 0 && !hasAllFields(target, requiredFields)) {
703
- return createIssue({
704
- code: 'assertion_failed',
705
- message: `Assertion "${check.description}" failed because required fields are missing.`,
706
- nodeId: node.id,
707
- severity: check.blocking ? 'blocking' : 'warning',
708
- detail: { mustContainFields: requiredFields },
709
- })
710
- }
711
-
712
- const equalityPath = typeof check.config.path === 'string' ? check.config.path : undefined
713
- if (equalityPath && 'equals' in check.config) {
714
- const actual = readPathValue(target, equalityPath)
715
- if (!Object.is(actual, check.config.equals)) {
716
- return createIssue({
717
- code: 'assertion_failed',
718
- message: `Assertion "${check.description}" failed at path "${equalityPath}".`,
719
- nodeId: node.id,
720
- severity: check.blocking ? 'blocking' : 'warning',
721
- detail: { path: equalityPath, expected: check.config.equals, actual },
722
- })
723
- }
724
- }
725
-
726
- const truthyPaths = Array.isArray(check.config.truthyPaths)
727
- ? check.config.truthyPaths.filter((entry): entry is string => typeof entry === 'string')
728
- : []
729
- if (truthyPaths.some((path) => !readPathValue(target, path))) {
730
- return createIssue({
731
- code: 'assertion_failed',
732
- message: `Assertion "${check.description}" expected truthy values that were not present.`,
733
- nodeId: node.id,
734
- severity: check.blocking ? 'blocking' : 'warning',
735
- detail: { truthyPaths },
736
- })
737
- }
738
-
739
- return null
740
- }
741
-
742
- if (check.type === 'tool-check') {
743
- const mode = typeof check.config.mode === 'string' ? check.config.mode : 'artifact-present'
744
- if (mode === 'artifact-present' && artifactName && !artifact) {
745
- return createIssue({
746
- code: 'required_artifact_missing',
747
- message: `Tool check "${check.description}" requires artifact "${artifactName}".`,
748
- nodeId: node.id,
749
- severity: check.blocking ? 'blocking' : 'warning',
750
- })
751
- }
752
- if (
753
- mode === 'artifact-kind' &&
754
- artifactName &&
755
- artifact &&
756
- typeof check.config.kind === 'string' &&
757
- artifact.kind !== check.config.kind
758
- ) {
759
- return createIssue({
760
- code: 'artifact_kind_mismatch',
761
- message: `Tool check "${check.description}" expected artifact "${artifactName}" to be kind "${check.config.kind}".`,
762
- nodeId: node.id,
763
- severity: check.blocking ? 'blocking' : 'warning',
764
- })
765
- }
766
- if (
767
- mode === 'min-artifact-count' &&
768
- typeof check.config.count === 'number' &&
769
- result.artifacts.length < check.config.count
770
- ) {
771
- return createIssue({
772
- code: 'required_artifact_missing',
773
- message: `Tool check "${check.description}" expected at least ${check.config.count} artifact(s).`,
774
- nodeId: node.id,
775
- severity: check.blocking ? 'blocking' : 'warning',
776
- })
777
- }
778
- return null
779
- }
780
-
781
- if (check.type === 'human-approval') {
782
- const approvedField = typeof check.config.approvedField === 'string' ? check.config.approvedField : 'approved'
783
- if (readPathValue(result.structuredOutput, approvedField) !== true) {
784
- return createIssue({
785
- code: 'human_rejected',
786
- message: `Human approval check "${check.description}" did not pass.`,
787
- nodeId: node.id,
788
- severity: check.blocking ? 'blocking' : 'warning',
789
- detail: { approvedField },
790
- })
791
- }
792
- return null
793
- }
794
-
795
- const resultField = typeof check.config.resultField === 'string' ? check.config.resultField : 'passed'
796
- if (readPathValue(result.structuredOutput, resultField) !== true) {
797
- return createIssue({
798
- code: 'schema_validation_failed',
799
- message: `LLM judge check "${check.description}" did not report success.`,
800
- nodeId: node.id,
801
- severity: check.blocking ? 'blocking' : 'warning',
802
- detail: { resultField },
803
- })
804
- }
805
- return null
806
- }
807
-
808
- private resolveFailureClass(blocking: PlanValidationIssueInput[]): PlanFailureClass | null {
809
- for (const issue of blocking) {
810
- if (issue.code === 'required_artifact_missing') return 'required_artifact_missing'
811
- if (issue.code === 'human_rejected') return 'human_rejected'
812
- if (issue.code === 'schema_validation_failed') return 'schema_validation_failed'
813
- }
814
- return blocking.length > 0 ? 'non_recoverable_logic_error' : null
815
- }
816
- }
817
-
818
- export const planValidatorService = new PlanValidatorService()