@lota-sdk/core 0.4.8 → 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/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  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
@@ -0,0 +1,125 @@
1
+ import { Context, Effect, Layer, Schema } from 'effect'
2
+
3
+ import { RedisServiceTag } from '../../effect/services'
4
+ import { toValidationError } from '../../effect/zod'
5
+ import type { RedisConnectionManager } from '../../redis/connection'
6
+ import { nowEpochMillis } from '../../utils/date-time'
7
+
8
+ const PlanWorkspaceEntrySchema = Schema.Struct({
9
+ value: Schema.Unknown,
10
+ version: Schema.Number,
11
+ writeSequence: Schema.Number,
12
+ nodeId: Schema.String,
13
+ timestamp: Schema.Number,
14
+ })
15
+
16
+ const PlanWorkspaceEntryJsonSchema = Schema.fromJsonString(PlanWorkspaceEntrySchema)
17
+
18
+ export type PlanWorkspaceEntry = {
19
+ value: unknown
20
+ version: number
21
+ writeSequence: number
22
+ nodeId: string
23
+ timestamp: number
24
+ }
25
+
26
+ const PLAN_WORKSPACE_KEY_PREFIX = 'plan-workspace:'
27
+
28
+ function parsePlanWorkspaceEntryEffect(
29
+ fieldKey: string,
30
+ raw: string,
31
+ ): Effect.Effect<PlanWorkspaceEntry, ReturnType<typeof toValidationError>> {
32
+ return Effect.try({
33
+ try: () => Schema.decodeUnknownSync(PlanWorkspaceEntryJsonSchema)(raw),
34
+ catch: (error) => toValidationError(error, `Invalid plan workspace entry JSON for "${fieldKey}"`),
35
+ })
36
+ }
37
+
38
+ export function makePlanWorkspaceService(redis: RedisConnectionManager) {
39
+ const writeEffect = (params: {
40
+ runId: string
41
+ nodeId: string
42
+ key: string
43
+ value: unknown
44
+ version: number
45
+ checkpointSequence: number
46
+ }) =>
47
+ Effect.gen(function* () {
48
+ const conn = redis.getConnection()
49
+ const hashKey = `${PLAN_WORKSPACE_KEY_PREFIX}${params.runId}`
50
+ const fieldKey = `${params.nodeId}:${params.key}`
51
+ const entry: PlanWorkspaceEntry = {
52
+ value: params.value,
53
+ version: params.version,
54
+ writeSequence: params.checkpointSequence,
55
+ nodeId: params.nodeId,
56
+ timestamp: nowEpochMillis(),
57
+ }
58
+ const serialized = yield* Schema.encodeEffect(PlanWorkspaceEntryJsonSchema)(entry)
59
+ yield* Effect.tryPromise(() => conn.hset(hashKey, fieldKey, serialized))
60
+ })
61
+
62
+ const snapshotReadEffect = (params: { runId: string; readerNodeId: string; snapshotSequence?: number }) =>
63
+ Effect.gen(function* () {
64
+ const conn = redis.getConnection()
65
+ const hashKey = `${PLAN_WORKSPACE_KEY_PREFIX}${params.runId}`
66
+ const nodePrefix = `${params.readerNodeId}:`
67
+ const all = yield* Effect.tryPromise(() => conn.hgetall(hashKey))
68
+ const result: Record<string, PlanWorkspaceEntry> = {}
69
+ for (const [fieldKey, raw] of Object.entries(all)) {
70
+ if (!fieldKey.startsWith(nodePrefix)) continue
71
+ const entry = yield* parsePlanWorkspaceEntryEffect(fieldKey, raw)
72
+ if (params.snapshotSequence !== undefined && entry.writeSequence > params.snapshotSequence) {
73
+ continue
74
+ }
75
+ result[fieldKey] = entry
76
+ }
77
+ return result
78
+ })
79
+
80
+ const currentReadEffect = (params: { runId: string; key?: string }) =>
81
+ Effect.gen(function* () {
82
+ const conn = redis.getConnection()
83
+ const hashKey = `${PLAN_WORKSPACE_KEY_PREFIX}${params.runId}`
84
+ const key = params.key
85
+ if (key !== undefined) {
86
+ const raw = yield* Effect.tryPromise(() => conn.hget(hashKey, key))
87
+ if (!raw) return {}
88
+ return { [key]: yield* parsePlanWorkspaceEntryEffect(key, raw) }
89
+ }
90
+ const all = yield* Effect.tryPromise(() => conn.hgetall(hashKey))
91
+ const result: Record<string, PlanWorkspaceEntry> = {}
92
+ for (const [k, v] of Object.entries(all)) {
93
+ result[k] = yield* parsePlanWorkspaceEntryEffect(k, v)
94
+ }
95
+ return result
96
+ })
97
+
98
+ const cleanupEffect = (runId: string) =>
99
+ Effect.asVoid(
100
+ Effect.tryPromise(() => {
101
+ const conn = redis.getConnection()
102
+ return conn.del(`${PLAN_WORKSPACE_KEY_PREFIX}${runId}`)
103
+ }),
104
+ )
105
+
106
+ return {
107
+ write: writeEffect,
108
+ snapshotRead: snapshotReadEffect,
109
+ currentRead: currentReadEffect,
110
+ cleanup: cleanupEffect,
111
+ }
112
+ }
113
+
114
+ export class PlanWorkspaceServiceTag extends Context.Service<
115
+ PlanWorkspaceServiceTag,
116
+ ReturnType<typeof makePlanWorkspaceService>
117
+ >()('PlanWorkspaceService') {}
118
+
119
+ export const PlanWorkspaceServiceLive = Layer.effect(
120
+ PlanWorkspaceServiceTag,
121
+ Effect.gen(function* () {
122
+ const redis = yield* RedisServiceTag
123
+ return makePlanWorkspaceService(redis)
124
+ }),
125
+ )
@@ -1,11 +1,16 @@
1
- import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, PluginPlanNodeOwner } from '@lota-sdk/shared'
1
+ import type { OwnershipDispatchContext, PlanNodeSpec, PluginPlanNodeOwner } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
4
+ import { BadRequestError } from '../effect/errors'
5
+ import { RuntimeConfigServiceTag } from '../effect/services'
3
6
  import type { LotaPlugin, PluginNodeExecutionParams } from '../runtime/plugin-types'
4
- import { getRuntimeConfig } from '../runtime/runtime-config'
5
- import type { PlanValidationIssueInput } from './plan-validator.service'
7
+ import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
8
+ import type { PlanValidationIssueInput } from './plan/plan-validator.service'
6
9
 
7
- function getPluginRuntime() {
8
- return (getRuntimeConfig().pluginRuntime ?? {}) as Record<string, LotaPlugin | undefined>
10
+ function isPluginOwner(
11
+ owner: PlanNodeSpec['owner'],
12
+ ): owner is Extract<PlanNodeSpec['owner'], { executorType: 'plugin' }> {
13
+ return owner.executorType === 'plugin'
9
14
  }
10
15
 
11
16
  function buildPluginExecutionParams(params: {
@@ -29,75 +34,99 @@ function buildPluginExecutionParams(params: {
29
34
  }
30
35
  }
31
36
 
32
- class PluginExecutorService {
33
- validateOwner(owner: PluginPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
34
- const plugin = getPluginRuntime()[owner.ref]
35
- if (!plugin) {
36
- return [
37
- {
38
- severity: 'blocking',
39
- code: 'plugin_executor_missing',
40
- message: `Node "${nodeId}" references unknown plugin executor "${owner.ref}".`,
41
- nodeId,
42
- detail: { pluginRef: owner.ref },
43
- },
44
- ]
45
- }
37
+ export function makePluginExecutorService(config: ResolvedLotaRuntimeConfig) {
38
+ function getPluginRuntime() {
39
+ return (config.pluginRuntime ?? {}) as Record<string, LotaPlugin | undefined>
40
+ }
46
41
 
47
- const nodeExecutor = plugin.nodeExecutor
48
- if (!nodeExecutor) {
49
- return [
50
- {
51
- severity: 'blocking',
52
- code: 'plugin_node_executor_missing',
53
- message: `Plugin "${owner.ref}" does not expose a node executor.`,
54
- nodeId,
55
- detail: { pluginRef: owner.ref },
56
- },
57
- ]
58
- }
42
+ return {
43
+ validateOwner(owner: PluginPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
44
+ const plugin = getPluginRuntime()[owner.ref]
45
+ if (!plugin) {
46
+ return [
47
+ {
48
+ severity: 'blocking',
49
+ code: 'plugin_executor_missing',
50
+ message: `Node "${nodeId}" references unknown plugin executor "${owner.ref}".`,
51
+ nodeId,
52
+ detail: { pluginRef: owner.ref },
53
+ },
54
+ ]
55
+ }
59
56
 
60
- if (!nodeExecutor.supportedOperations.includes(owner.operation)) {
61
- return [
62
- {
63
- severity: 'blocking',
64
- code: 'plugin_operation_missing',
65
- message: `Plugin "${owner.ref}" does not support operation "${owner.operation}".`,
66
- nodeId,
67
- detail: { pluginRef: owner.ref, operation: owner.operation },
68
- },
69
- ]
70
- }
57
+ const nodeExecutor = plugin.nodeExecutor
58
+ if (!nodeExecutor) {
59
+ return [
60
+ {
61
+ severity: 'blocking',
62
+ code: 'plugin_node_executor_missing',
63
+ message: `Plugin "${owner.ref}" does not expose a node executor.`,
64
+ nodeId,
65
+ detail: { pluginRef: owner.ref },
66
+ },
67
+ ]
68
+ }
71
69
 
72
- return []
73
- }
70
+ if (!nodeExecutor.supportedOperations.includes(owner.operation)) {
71
+ return [
72
+ {
73
+ severity: 'blocking',
74
+ code: 'plugin_operation_missing',
75
+ message: `Plugin "${owner.ref}" does not support operation "${owner.operation}".`,
76
+ nodeId,
77
+ detail: { pluginRef: owner.ref, operation: owner.operation },
78
+ },
79
+ ]
80
+ }
74
81
 
75
- async executeNode(params: {
76
- nodeSpec: PlanNodeSpec
77
- resolvedInput: Record<string, unknown>
78
- context: OwnershipDispatchContext
79
- }): Promise<PlanNodeResult> {
80
- if (params.nodeSpec.owner.executorType !== 'plugin') {
81
- throw new Error(`PluginExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
82
- }
82
+ return []
83
+ },
83
84
 
84
- const plugin = getPluginRuntime()[params.nodeSpec.owner.ref]
85
- const nodeExecutor = plugin?.nodeExecutor
86
- if (!plugin || !nodeExecutor || !nodeExecutor.supportedOperations.includes(params.nodeSpec.owner.operation)) {
87
- throw new Error(
88
- `Plugin executor ${params.nodeSpec.owner.ref}.${params.nodeSpec.owner.operation} is not registered.`,
89
- )
90
- }
85
+ executeNode(params: {
86
+ nodeSpec: PlanNodeSpec
87
+ resolvedInput: Record<string, unknown>
88
+ context: OwnershipDispatchContext
89
+ }) {
90
+ return Effect.gen(function* () {
91
+ const owner = params.nodeSpec.owner
92
+ if (!isPluginOwner(owner)) {
93
+ return yield* new BadRequestError({
94
+ message: `PluginExecutor cannot execute owner type "${owner.executorType}".`,
95
+ })
96
+ }
91
97
 
92
- return nodeExecutor.executeNode(
93
- buildPluginExecutionParams({
94
- owner: params.nodeSpec.owner,
95
- nodeSpec: params.nodeSpec,
96
- resolvedInput: params.resolvedInput,
97
- context: params.context,
98
- }),
99
- )
98
+ const plugin = getPluginRuntime()[owner.ref]
99
+ const nodeExecutor = plugin?.nodeExecutor
100
+ if (!plugin || !nodeExecutor || !nodeExecutor.supportedOperations.includes(owner.operation)) {
101
+ return yield* new BadRequestError({
102
+ message: `Plugin executor ${owner.ref}.${owner.operation} is not registered.`,
103
+ })
104
+ }
105
+
106
+ return yield* Effect.tryPromise(() =>
107
+ nodeExecutor.executeNode(
108
+ buildPluginExecutionParams({
109
+ owner,
110
+ nodeSpec: params.nodeSpec,
111
+ resolvedInput: params.resolvedInput,
112
+ context: params.context,
113
+ }),
114
+ ),
115
+ )
116
+ })
117
+ },
100
118
  }
101
119
  }
102
120
 
103
- export const pluginExecutorService = new PluginExecutorService()
121
+ export class PluginExecutorServiceTag extends Context.Service<
122
+ PluginExecutorServiceTag,
123
+ ReturnType<typeof makePluginExecutorService>
124
+ >()('PluginExecutorService') {}
125
+
126
+ export const PluginExecutorServiceLive = Layer.effect(
127
+ PluginExecutorServiceTag,
128
+ Effect.gen(function* () {
129
+ const runtimeConfig = yield* RuntimeConfigServiceTag
130
+ return makePluginExecutorService(runtimeConfig)
131
+ }),
132
+ )
@@ -1,10 +1,12 @@
1
1
  import { NodeQualityMetricsSchema } from '@lota-sdk/shared'
2
2
  import type { NodeQualityMetrics } from '@lota-sdk/shared'
3
+ import { Context, Effect, Layer } from 'effect'
3
4
  import { z } from 'zod'
4
5
 
5
6
  import { ensureRecordId } from '../db/record-id'
6
- import { databaseService } from '../db/service'
7
+ import type { SurrealDBService } from '../db/service'
7
8
  import { TABLES } from '../db/tables'
9
+ import { DatabaseServiceTag } from '../effect/services'
8
10
 
9
11
  const QualityMetricRowSchema = NodeQualityMetricsSchema.extend({
10
12
  id: z.unknown(),
@@ -15,118 +17,134 @@ const QualityMetricRowSchema = NodeQualityMetricsSchema.extend({
15
17
  updatedAt: z.coerce.date().optional(),
16
18
  })
17
19
 
18
- class QualityMetricsService {
19
- async recordNodeMetrics(params: {
20
+ export function makeQualityMetricsService(db: SurrealDBService) {
21
+ const recordNodeMetricsEffect = (params: {
20
22
  organizationId: string
21
23
  runId: string
22
24
  nodeId: string
23
25
  metrics: NodeQualityMetrics
24
- }): Promise<void> {
25
- await databaseService.create(
26
- TABLES.QUALITY_METRIC,
27
- {
28
- organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
29
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
30
- nodeId: params.nodeId,
31
- ownerRef: params.metrics.ownerRef,
32
- ownerType: params.metrics.ownerType,
33
- nodeType: params.metrics.nodeType,
34
- executionTimeMs: params.metrics.executionTimeMs,
35
- attemptCount: params.metrics.attemptCount,
36
- artifactCount: params.metrics.artifactCount,
37
- validationIssueCount: params.metrics.validationIssueCount,
38
- },
39
- QualityMetricRowSchema,
40
- )
41
- }
42
-
43
- async recordCycleMetrics(params: { organizationId: string; runId: string }): Promise<void> {
44
- const existing = await databaseService.findMany(
45
- TABLES.QUALITY_METRIC,
46
- { runId: ensureRecordId(params.runId, TABLES.PLAN_RUN) },
47
- QualityMetricRowSchema,
48
- { orderBy: 'createdAt', orderDir: 'ASC' },
49
- )
50
-
51
- if (!Array.isArray(existing) || existing.length === 0) return
52
-
53
- const totalExecutionTime = existing.reduce(
54
- (sum, m) => sum + (typeof m.executionTimeMs === 'number' ? m.executionTimeMs : 0),
55
- 0,
56
- )
57
- const totalAttempts = existing.reduce(
58
- (sum, m) => sum + (typeof m.attemptCount === 'number' ? m.attemptCount : 0),
59
- 0,
60
- )
61
- const totalIssues = existing.reduce(
62
- (sum, m) => sum + (typeof m.validationIssueCount === 'number' ? m.validationIssueCount : 0),
63
- 0,
26
+ }) =>
27
+ Effect.asVoid(
28
+ db.create(
29
+ TABLES.QUALITY_METRIC,
30
+ {
31
+ organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
32
+ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
33
+ nodeId: params.nodeId,
34
+ ownerRef: params.metrics.ownerRef,
35
+ ownerType: params.metrics.ownerType,
36
+ nodeType: params.metrics.nodeType,
37
+ executionTimeMs: params.metrics.executionTimeMs,
38
+ attemptCount: params.metrics.attemptCount,
39
+ artifactCount: params.metrics.artifactCount,
40
+ validationIssueCount: params.metrics.validationIssueCount,
41
+ },
42
+ QualityMetricRowSchema,
43
+ ),
64
44
  )
65
45
 
66
- await databaseService.create(
46
+ const recordCycleMetricsEffect = (params: { organizationId: string; runId: string }) =>
47
+ Effect.gen(function* () {
48
+ const existing = yield* db.findMany(
49
+ TABLES.QUALITY_METRIC,
50
+ { runId: ensureRecordId(params.runId, TABLES.PLAN_RUN) },
51
+ QualityMetricRowSchema,
52
+ { orderBy: 'createdAt', orderDir: 'ASC' },
53
+ )
54
+
55
+ if (!Array.isArray(existing) || existing.length === 0) return
56
+
57
+ const totalExecutionTime = existing.reduce(
58
+ (sum, m) => sum + (typeof m.executionTimeMs === 'number' ? m.executionTimeMs : 0),
59
+ 0,
60
+ )
61
+ const totalAttempts = existing.reduce(
62
+ (sum, m) => sum + (typeof m.attemptCount === 'number' ? m.attemptCount : 0),
63
+ 0,
64
+ )
65
+ const totalIssues = existing.reduce(
66
+ (sum, m) => sum + (typeof m.validationIssueCount === 'number' ? m.validationIssueCount : 0),
67
+ 0,
68
+ )
69
+
70
+ yield* Effect.asVoid(
71
+ db.create(
72
+ TABLES.QUALITY_METRIC,
73
+ {
74
+ organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
75
+ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
76
+ ownerRef: 'cycle-summary',
77
+ ownerType: 'system',
78
+ nodeType: 'action',
79
+ executionTimeMs: totalExecutionTime,
80
+ attemptCount: totalAttempts,
81
+ artifactCount: existing.length,
82
+ validationIssueCount: totalIssues,
83
+ },
84
+ QualityMetricRowSchema,
85
+ ),
86
+ )
87
+ })
88
+
89
+ const getMetricsByOwnerEffect = (params: { organizationId: string; ownerRef: string; limit?: number }) =>
90
+ db.findMany(
67
91
  TABLES.QUALITY_METRIC,
68
- {
69
- organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
70
- runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
71
- ownerRef: 'cycle-summary',
72
- ownerType: 'system',
73
- nodeType: 'action',
74
- executionTimeMs: totalExecutionTime,
75
- attemptCount: totalAttempts,
76
- artifactCount: existing.length,
77
- validationIssueCount: totalIssues,
78
- },
92
+ { organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION), ownerRef: params.ownerRef },
79
93
  QualityMetricRowSchema,
94
+ { orderBy: 'createdAt', orderDir: 'DESC', limit: params.limit ?? 20 },
80
95
  )
81
- }
82
96
 
83
- detectRegression(params: {
84
- metrics: Array<{ executionTimeMs: number; validationIssueCount: number }>
85
- window?: number
86
- }): { detected: boolean; trend?: 'improving' | 'stable' | 'regressing' } {
87
- const window = params.window ?? 5
88
- const recent = params.metrics.slice(-window)
89
- if (recent.length < 2) return { detected: false, trend: 'stable' }
97
+ return {
98
+ recordNodeMetrics: recordNodeMetricsEffect,
99
+ recordCycleMetrics: recordCycleMetricsEffect,
100
+ getMetricsByOwner: getMetricsByOwnerEffect,
90
101
 
91
- const mid = Math.floor(recent.length / 2)
92
- const firstHalf = recent.slice(0, mid)
93
- const secondHalf = recent.slice(mid)
102
+ detectRegression(params: {
103
+ metrics: Array<{ executionTimeMs: number; validationIssueCount: number }>
104
+ window?: number
105
+ }): { detected: boolean; trend?: 'improving' | 'stable' | 'regressing' } {
106
+ const window = params.window ?? 5
107
+ const recent = params.metrics.slice(-window)
108
+ if (recent.length < 2) return { detected: false, trend: 'stable' }
94
109
 
95
- const avgFirst = firstHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / firstHalf.length
96
- const avgSecond = secondHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / secondHalf.length
110
+ const mid = Math.floor(recent.length / 2)
111
+ const firstHalf = recent.slice(0, mid)
112
+ const secondHalf = recent.slice(mid)
97
113
 
98
- const timeFirst = firstHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / firstHalf.length
99
- const timeSecond = secondHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / secondHalf.length
114
+ const avgFirst = firstHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / firstHalf.length
115
+ const avgSecond = secondHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / secondHalf.length
100
116
 
101
- const issueRegression = avgFirst > 0 && avgSecond > avgFirst * 1.5
102
- const timeRegression = timeFirst > 0 && timeSecond > timeFirst * 2
117
+ const timeFirst = firstHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / firstHalf.length
118
+ const timeSecond = secondHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / secondHalf.length
103
119
 
104
- if (issueRegression || timeRegression) {
105
- return { detected: true, trend: 'regressing' }
106
- }
120
+ const issueRegression = avgFirst > 0 && avgSecond > avgFirst * 1.5
121
+ const timeRegression = timeFirst > 0 && timeSecond > timeFirst * 2
107
122
 
108
- const issueImproving = avgFirst > 0 && avgSecond < avgFirst * 0.7
109
- const timeImproving = timeFirst > 0 && timeSecond < timeFirst * 0.7
123
+ if (issueRegression || timeRegression) {
124
+ return { detected: true, trend: 'regressing' }
125
+ }
110
126
 
111
- if (issueImproving || timeImproving) {
112
- return { detected: false, trend: 'improving' }
113
- }
127
+ const issueImproving = avgFirst > 0 && avgSecond < avgFirst * 0.7
128
+ const timeImproving = timeFirst > 0 && timeSecond < timeFirst * 0.7
114
129
 
115
- return { detected: false, trend: 'stable' }
116
- }
130
+ if (issueImproving || timeImproving) {
131
+ return { detected: false, trend: 'improving' }
132
+ }
117
133
 
118
- async getMetricsByOwner(params: {
119
- organizationId: string
120
- ownerRef: string
121
- limit?: number
122
- }): Promise<z.infer<typeof QualityMetricRowSchema>[]> {
123
- return databaseService.findMany(
124
- TABLES.QUALITY_METRIC,
125
- { organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION), ownerRef: params.ownerRef },
126
- QualityMetricRowSchema,
127
- { orderBy: 'createdAt', orderDir: 'DESC', limit: params.limit ?? 20 },
128
- )
134
+ return { detected: false, trend: 'stable' }
135
+ },
129
136
  }
130
137
  }
131
138
 
132
- export const qualityMetricsService = new QualityMetricsService()
139
+ export class QualityMetricsServiceTag extends Context.Service<
140
+ QualityMetricsServiceTag,
141
+ ReturnType<typeof makeQualityMetricsService>
142
+ >()('QualityMetricsService') {}
143
+
144
+ export const QualityMetricsServiceLive = Layer.effect(
145
+ QualityMetricsServiceTag,
146
+ Effect.gen(function* () {
147
+ const db = yield* DatabaseServiceTag
148
+ return makeQualityMetricsService(db)
149
+ }),
150
+ )