@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /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()