@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,3 +1,4 @@
1
+ import { isSafePath } from '@lota-sdk/shared'
1
2
  import type {
2
3
  PlanArtifactSubmission,
3
4
  PlanNodeResult,
@@ -6,8 +7,12 @@ import type {
6
7
  WriteIntent,
7
8
  WriteIntentAction,
8
9
  } from '@lota-sdk/shared'
10
+ import { Context, Effect, Layer } from 'effect'
9
11
 
10
12
  import { serverLogger } from '../config/logger'
13
+ import { BadRequestError } from '../effect/errors'
14
+ import { nowDate } from '../utils/date-time'
15
+ import { cloneToNullProtoRecord, createNullProtoRecord, setPathValue } from '../utils/null-proto-record'
11
16
 
12
17
  export type WorkspaceEntryValidation = 'validated' | 'unvalidated'
13
18
 
@@ -36,121 +41,159 @@ export interface NodeWorkspace {
36
41
  deliverables: Map<string, WorkspaceEntry>
37
42
  }
38
43
 
39
- class NodeWorkspaceService {
40
- initialize(params: {
41
- nodeSpec: PlanNodeSpec
42
- resolvedInput: Record<string, unknown>
43
- inputArtifacts: PlanArtifactSubmission[]
44
- schemaRegistry: PlanSchemaRegistry
45
- }): NodeWorkspace {
46
- const ctx = Object.freeze({
47
- resolvedInput: params.resolvedInput,
48
- inputArtifacts: params.inputArtifacts,
49
- schemaRegistry: params.schemaRegistry,
50
- nodeSpec: params.nodeSpec,
51
- })
52
-
53
- return {
54
- nodeId: params.nodeSpec.id,
55
- ctx,
56
- work: new Map(),
57
- sys: { startedAt: new Date(), correctionCounts: new Map(), writeLog: [] },
58
- deliverables: new Map(),
59
- }
44
+ function initializeWorkspace(params: {
45
+ nodeSpec: PlanNodeSpec
46
+ resolvedInput: Record<string, unknown>
47
+ inputArtifacts: PlanArtifactSubmission[]
48
+ schemaRegistry: PlanSchemaRegistry
49
+ }): NodeWorkspace {
50
+ const ctx = Object.freeze({
51
+ resolvedInput: params.resolvedInput,
52
+ inputArtifacts: params.inputArtifacts,
53
+ schemaRegistry: params.schemaRegistry,
54
+ nodeSpec: params.nodeSpec,
55
+ })
56
+
57
+ return {
58
+ nodeId: params.nodeSpec.id,
59
+ ctx,
60
+ work: new Map(),
61
+ sys: { startedAt: nowDate(), correctionCounts: new Map(), writeLog: [] },
62
+ deliverables: new Map(),
60
63
  }
64
+ }
61
65
 
62
- stageWrite(workspace: NodeWorkspace, intent: WriteIntent, validation: WorkspaceEntryValidation): void {
63
- const existing = workspace.deliverables.get(intent.targetPath)
66
+ export function makeNodeWorkspaceService() {
67
+ const stageWriteEffect = (
68
+ workspace: NodeWorkspace,
69
+ intent: WriteIntent,
70
+ validation: WorkspaceEntryValidation,
71
+ ): Effect.Effect<void, BadRequestError> =>
72
+ Effect.gen(function* () {
73
+ if (!isSafePath(intent.targetPath)) {
74
+ return yield* new BadRequestError({ message: `Unsafe path "${intent.targetPath}"` })
75
+ }
76
+
77
+ const existing = workspace.deliverables.get(intent.targetPath)
78
+
79
+ if (intent.action === 'append' && existing && Array.isArray(existing.payload) && Array.isArray(intent.payload)) {
80
+ existing.payload = [...(existing.payload as unknown[]), ...intent.payload]
81
+ existing.action = intent.action
82
+ existing.validation = validation
83
+ existing.stagedAt = nowDate()
84
+ } else {
85
+ workspace.deliverables.set(intent.targetPath, {
86
+ targetPath: intent.targetPath,
87
+ action: intent.action,
88
+ payload: intent.payload,
89
+ validation,
90
+ stagedAt: nowDate(),
91
+ })
92
+ }
64
93
 
65
- if (intent.action === 'append' && existing && Array.isArray(existing.payload) && Array.isArray(intent.payload)) {
66
- existing.payload = [...(existing.payload as unknown[]), ...intent.payload]
67
- existing.action = intent.action
68
- existing.validation = validation
69
- existing.stagedAt = new Date()
70
- } else {
71
- workspace.deliverables.set(intent.targetPath, {
94
+ workspace.sys.writeLog.push({
72
95
  targetPath: intent.targetPath,
73
96
  action: intent.action,
74
- payload: intent.payload,
75
- validation,
76
- stagedAt: new Date(),
97
+ timestamp: nowDate(),
98
+ result: 'accepted',
77
99
  })
78
- }
79
-
80
- workspace.sys.writeLog.push({
81
- targetPath: intent.targetPath,
82
- action: intent.action,
83
- timestamp: new Date(),
84
- result: 'accepted',
85
100
  })
86
- }
87
101
 
88
- finalize(workspace: NodeWorkspace): PlanNodeResult & { isComplete: boolean } {
89
- const artifacts: PlanArtifactSubmission[] = []
90
- let structuredOutput: Record<string, unknown> | undefined
91
- let allValidated = true
92
- const requiredNames = new Set(workspace.ctx.nodeSpec.deliverables.filter((d) => d.required).map((d) => d.name))
93
- const presentRequired = new Set<string>()
102
+ const finalizeEffect = (
103
+ workspace: NodeWorkspace,
104
+ ): Effect.Effect<PlanNodeResult & { isComplete: boolean }, BadRequestError> =>
105
+ Effect.gen(function* () {
106
+ const artifacts: PlanArtifactSubmission[] = []
107
+ let structuredOutput: Record<string, unknown> | undefined
108
+ let allValidated = true
109
+ const requiredNames = new Set(workspace.ctx.nodeSpec.deliverables.filter((d) => d.required).map((d) => d.name))
110
+ const presentRequired = new Set<string>()
111
+
112
+ for (const [targetPath, entry] of workspace.deliverables) {
113
+ if (entry.validation === 'unvalidated') {
114
+ allValidated = false
115
+ }
94
116
 
95
- for (const [targetPath, entry] of workspace.deliverables) {
96
- if (entry.validation === 'unvalidated') {
97
- allValidated = false
98
- }
117
+ if (targetPath.startsWith('structuredOutput')) {
118
+ const subPath = targetPath.replace(/^structuredOutput\.?/, '')
119
+ if (subPath && !isSafePath(subPath)) {
120
+ return yield* new BadRequestError({ message: `Unsafe path "${targetPath}"` })
121
+ }
122
+
123
+ if (!structuredOutput) structuredOutput = createNullProtoRecord()
124
+ if (subPath) {
125
+ setPathValue(structuredOutput, subPath, entry.payload)
126
+ } else {
127
+ structuredOutput =
128
+ typeof entry.payload === 'object' && entry.payload !== null && !Array.isArray(entry.payload)
129
+ ? cloneToNullProtoRecord(entry.payload as Record<string, unknown>)
130
+ : structuredOutput
131
+ }
132
+ continue
133
+ }
99
134
 
100
- if (targetPath.startsWith('structuredOutput')) {
101
- if (!structuredOutput) structuredOutput = {}
102
- const subPath = targetPath.replace(/^structuredOutput\.?/, '')
103
- if (subPath) {
104
- structuredOutput[subPath] = entry.payload
105
- } else {
106
- structuredOutput =
107
- typeof entry.payload === 'object' && entry.payload !== null && !Array.isArray(entry.payload)
108
- ? (entry.payload as Record<string, unknown>)
109
- : structuredOutput
135
+ const spec = workspace.ctx.nodeSpec.deliverables.find((d) => d.name === targetPath)
136
+ if (!spec) {
137
+ serverLogger.warn`Write to unknown deliverable path "${targetPath}" in node ${workspace.nodeId} — skipping`
138
+ continue
110
139
  }
111
- continue
112
- }
113
140
 
114
- const spec = workspace.ctx.nodeSpec.deliverables.find((d) => d.name === targetPath)
115
- if (!spec) {
116
- serverLogger.warn`Write to unknown deliverable path "${targetPath}" in node ${workspace.nodeId} — skipping`
117
- continue
118
- }
141
+ if (requiredNames.has(targetPath)) {
142
+ presentRequired.add(targetPath)
143
+ }
119
144
 
120
- if (requiredNames.has(targetPath)) {
121
- presentRequired.add(targetPath)
145
+ artifacts.push({
146
+ name: spec.name,
147
+ kind: spec.kind,
148
+ description: spec.description,
149
+ payload:
150
+ typeof entry.payload === 'string'
151
+ ? { content: entry.payload }
152
+ : typeof entry.payload === 'object' && entry.payload !== null
153
+ ? (entry.payload as Record<string, unknown> | unknown[])
154
+ : undefined,
155
+ })
122
156
  }
123
157
 
124
- artifacts.push({
125
- name: spec.name,
126
- kind: spec.kind,
127
- description: spec.description,
128
- payload:
129
- typeof entry.payload === 'string'
130
- ? { content: entry.payload }
131
- : typeof entry.payload === 'object' && entry.payload !== null
132
- ? (entry.payload as Record<string, unknown> | unknown[])
133
- : undefined,
134
- })
135
- }
136
-
137
- const allRequiredPresent = requiredNames.size === presentRequired.size
138
- const isComplete = allRequiredPresent && allValidated
158
+ const allRequiredPresent = requiredNames.size === presentRequired.size
159
+ const isComplete = allRequiredPresent && allValidated
139
160
 
140
- return {
141
- notes: isComplete ? 'All deliverables completed.' : 'Partial deliverables completed.',
142
- structuredOutput,
143
- artifacts,
144
- isComplete,
145
- }
146
- }
161
+ return {
162
+ notes: isComplete ? 'All deliverables completed.' : 'Partial deliverables completed.',
163
+ structuredOutput,
164
+ artifacts,
165
+ isComplete,
166
+ }
167
+ })
147
168
 
148
- cleanup(workspace: NodeWorkspace): void {
149
- workspace.work.clear()
150
- workspace.deliverables.clear()
151
- workspace.sys.writeLog.length = 0
152
- workspace.sys.correctionCounts.clear()
169
+ return {
170
+ initialize(params: {
171
+ nodeSpec: PlanNodeSpec
172
+ resolvedInput: Record<string, unknown>
173
+ inputArtifacts: PlanArtifactSubmission[]
174
+ schemaRegistry: PlanSchemaRegistry
175
+ }): NodeWorkspace {
176
+ return initializeWorkspace(params)
177
+ },
178
+
179
+ stageWrite: stageWriteEffect,
180
+
181
+ finalize: finalizeEffect,
182
+
183
+ cleanup(workspace: NodeWorkspace): void {
184
+ workspace.work.clear()
185
+ workspace.deliverables.clear()
186
+ workspace.sys.writeLog.length = 0
187
+ workspace.sys.correctionCounts.clear()
188
+ },
153
189
  }
154
190
  }
155
191
 
156
- export const nodeWorkspaceService = new NodeWorkspaceService()
192
+ export const nodeWorkspaceService = makeNodeWorkspaceService()
193
+
194
+ export class NodeWorkspaceServiceTag extends Context.Service<
195
+ NodeWorkspaceServiceTag,
196
+ ReturnType<typeof makeNodeWorkspaceService>
197
+ >()('NodeWorkspaceService') {}
198
+
199
+ export const NodeWorkspaceServiceLive = Layer.succeed(NodeWorkspaceServiceTag, nodeWorkspaceService)
@@ -1,4 +1,5 @@
1
1
  import type { NotificationSeverity } from '@lota-sdk/shared'
2
+ import { Context, Layer } from 'effect'
2
3
 
3
4
  export interface NotificationPayload {
4
5
  organizationId: string
@@ -18,22 +19,22 @@ export interface NotificationService {
18
19
  escalate(payload: NotificationPayload): Promise<void>
19
20
  }
20
21
 
21
- export class NoOpNotificationService implements NotificationService {
22
- async notify(_payload: NotificationPayload): Promise<void> {}
23
- async remind(_payload: NotificationPayload): Promise<void> {}
24
- async escalate(_payload: NotificationPayload): Promise<void> {}
25
- }
26
-
27
- let _notificationService: NotificationService | null = null
28
-
29
- export function configureNotificationService(service: NotificationService | null): void {
30
- _notificationService = service
22
+ export function makeNotificationService(): NotificationService {
23
+ return {
24
+ notify(_payload: NotificationPayload): Promise<void> {
25
+ return Promise.resolve()
26
+ },
27
+ remind(_payload: NotificationPayload): Promise<void> {
28
+ return Promise.resolve()
29
+ },
30
+ escalate(_payload: NotificationPayload): Promise<void> {
31
+ return Promise.resolve()
32
+ },
33
+ }
31
34
  }
32
35
 
33
- export function getNotificationService(): NotificationService {
34
- if (_notificationService === null) {
35
- throw new Error('Notification service is not configured.')
36
- }
36
+ export class NotificationServiceTag extends Context.Service<NotificationServiceTag, NotificationService>()(
37
+ 'NotificationService',
38
+ ) {}
37
39
 
38
- return _notificationService
39
- }
40
+ export const NotificationServiceLive = Layer.succeed(NotificationServiceTag, makeNotificationService())
@@ -1,10 +1,12 @@
1
1
  import { recordIdSchema, recordIdStringSchema } from '@lota-sdk/shared'
2
+ import { Context, Schema, Effect, Layer } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
- import { BaseService } from '../db/base.service'
5
5
  import { ensureRecordId, recordIdToString } from '../db/record-id'
6
6
  import type { RecordIdInput, RecordIdRef } from '../db/record-id'
7
+ import type { SurrealDBService } from '../db/service'
7
8
  import { TABLES } from '../db/tables'
9
+ import { DatabaseServiceTag } from '../effect/services'
8
10
  import { toIsoDateTimeString } from '../utils/date-time'
9
11
 
10
12
  const organizationMemberRecordSchema = z.object({
@@ -26,96 +28,148 @@ const sdkOrganizationMemberSchema = z.object({
26
28
  type SdkOrganizationMemberRecord = z.infer<typeof organizationMemberRecordSchema>
27
29
  export type SdkOrganizationMember = z.infer<typeof sdkOrganizationMemberSchema>
28
30
 
29
- class OrganizationMemberService extends BaseService<typeof organizationMemberRecordSchema> {
30
- constructor() {
31
- super(TABLES.ORGANIZATION_MEMBER, organizationMemberRecordSchema)
32
- }
31
+ class OrganizationMemberServiceError extends Schema.TaggedErrorClass<OrganizationMemberServiceError>()(
32
+ 'OrganizationMemberServiceError',
33
+ { operation: Schema.String, cause: Schema.Defect },
34
+ ) {}
33
35
 
34
- toPublic(record: SdkOrganizationMemberRecord): SdkOrganizationMember {
35
- return sdkOrganizationMemberSchema.parse({
36
- id: recordIdToString(
37
- ensureRecordId(record.id as RecordIdInput, TABLES.ORGANIZATION_MEMBER),
38
- TABLES.ORGANIZATION_MEMBER,
39
- ),
40
- userId: recordIdToString(ensureRecordId(record.in as RecordIdInput, TABLES.USER), TABLES.USER),
41
- organizationId: recordIdToString(
42
- ensureRecordId(record.out as RecordIdInput, TABLES.ORGANIZATION),
43
- TABLES.ORGANIZATION,
44
- ),
45
- role: record.role,
46
- createdAt: toIsoDateTimeString(record.createdAt),
47
- })
36
+ function toOrganizationMemberServiceError(operation: string, cause: unknown): OrganizationMemberServiceError {
37
+ return new OrganizationMemberServiceError({ operation, cause })
38
+ }
39
+
40
+ function toPublic(record: SdkOrganizationMemberRecord): SdkOrganizationMember {
41
+ return sdkOrganizationMemberSchema.parse({
42
+ id: recordIdToString(
43
+ ensureRecordId(record.id as RecordIdInput, TABLES.ORGANIZATION_MEMBER),
44
+ TABLES.ORGANIZATION_MEMBER,
45
+ ),
46
+ userId: recordIdToString(ensureRecordId(record.in as RecordIdInput, TABLES.USER), TABLES.USER),
47
+ organizationId: recordIdToString(
48
+ ensureRecordId(record.out as RecordIdInput, TABLES.ORGANIZATION),
49
+ TABLES.ORGANIZATION,
50
+ ),
51
+ role: record.role,
52
+ createdAt: toIsoDateTimeString(record.createdAt),
53
+ })
54
+ }
55
+
56
+ export function makeOrganizationMemberService(db: SurrealDBService) {
57
+ function findMembershipRecords(
58
+ filter: Record<string, unknown>,
59
+ options?: { limit?: number; orderBy?: string; orderDir?: 'ASC' | 'DESC' },
60
+ ) {
61
+ return db
62
+ .findMany(TABLES.ORGANIZATION_MEMBER, filter, organizationMemberRecordSchema, options)
63
+ .pipe(Effect.mapError((cause) => toOrganizationMemberServiceError('findMembershipRecords', cause)))
48
64
  }
49
65
 
50
- async addMembership(params: {
51
- userId: RecordIdInput
52
- organizationId: RecordIdInput
53
- role: 'owner' | 'member'
54
- }): Promise<SdkOrganizationMember> {
66
+ function addMembership(params: { userId: RecordIdInput; organizationId: RecordIdInput; role: 'owner' | 'member' }) {
55
67
  const userRef = ensureRecordId(params.userId, TABLES.USER)
56
68
  const organizationRef = ensureRecordId(params.organizationId, TABLES.ORGANIZATION)
57
- const existing = (await this.findAll({ in: userRef, out: organizationRef }, { limit: 1 })).at(0)
58
-
59
- if (existing) {
60
- return this.toPublic(await this.update(existing.id, { role: params.role }))
61
- }
62
-
63
- const related = await this.databaseService.relate(userRef, TABLES.ORGANIZATION_MEMBER, organizationRef, {
64
- role: params.role,
65
- })
66
- if (!related) {
67
- throw new Error(
68
- `Failed to create membership for ${recordIdToString(userRef, TABLES.USER)} in ${recordIdToString(
69
- organizationRef,
70
- TABLES.ORGANIZATION,
71
- )}`,
69
+ return Effect.gen(function* () {
70
+ const records = yield* findMembershipRecords({ in: userRef, out: organizationRef }, { limit: 1 }).pipe(
71
+ Effect.mapError((cause) => toOrganizationMemberServiceError('addMembership.findExisting', cause)),
72
72
  )
73
- }
74
-
75
- const record = organizationMemberRecordSchema.parse(related)
76
-
77
- return this.toPublic(record)
73
+ const existing = records.at(0)
74
+
75
+ if (existing) {
76
+ const updated = yield* db
77
+ .update(TABLES.ORGANIZATION_MEMBER, existing.id, { role: params.role }, organizationMemberRecordSchema)
78
+ .pipe(Effect.mapError((cause) => toOrganizationMemberServiceError('addMembership.update', cause)))
79
+ if (!updated) {
80
+ return yield* new OrganizationMemberServiceError({
81
+ operation: 'addMembership.update',
82
+ cause: new Error(
83
+ `Failed to update membership for ${recordIdToString(userRef, TABLES.USER)} in ${recordIdToString(organizationRef, TABLES.ORGANIZATION)}`,
84
+ ),
85
+ })
86
+ }
87
+ return toPublic(updated)
88
+ }
89
+
90
+ const related = yield* db
91
+ .relate(userRef, TABLES.ORGANIZATION_MEMBER, organizationRef, { role: params.role })
92
+ .pipe(Effect.mapError((cause) => toOrganizationMemberServiceError('addMembership.relate', cause)))
93
+ if (!related) {
94
+ return yield* new OrganizationMemberServiceError({
95
+ operation: 'addMembership.relate',
96
+ cause: new Error(
97
+ `Failed to create membership for ${recordIdToString(userRef, TABLES.USER)} in ${recordIdToString(organizationRef, TABLES.ORGANIZATION)}`,
98
+ ),
99
+ })
100
+ }
101
+
102
+ return toPublic(organizationMemberRecordSchema.parse(related))
103
+ })
78
104
  }
79
105
 
80
- async listMembershipsForOrganization(organizationId: RecordIdRef): Promise<SdkOrganizationMember[]> {
106
+ function listMembershipsForOrganization(organizationId: RecordIdRef) {
81
107
  const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
82
- return (await this.findAll({ out: organizationRef }, { orderBy: 'createdAt', orderDir: 'ASC' })).map((record) =>
83
- this.toPublic(record),
108
+ return findMembershipRecords({ out: organizationRef }, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
109
+ Effect.mapError((cause) => toOrganizationMemberServiceError('listMembershipsForOrganization', cause)),
110
+ Effect.map((records) => records.map(toPublic)),
84
111
  )
85
112
  }
86
113
 
87
- async listMembershipsForUser(userId: RecordIdRef): Promise<SdkOrganizationMember[]> {
114
+ function listMembershipsForUser(userId: RecordIdRef) {
88
115
  const userRef = ensureRecordId(userId, TABLES.USER)
89
- return (await this.findAll({ in: userRef }, { orderBy: 'createdAt', orderDir: 'ASC' })).map((record) =>
90
- this.toPublic(record),
116
+ return findMembershipRecords({ in: userRef }, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
117
+ Effect.mapError((cause) => toOrganizationMemberServiceError('listMembershipsForUser', cause)),
118
+ Effect.map((records) => records.map(toPublic)),
91
119
  )
92
120
  }
93
121
 
94
- async getMembership(userId: RecordIdInput, organizationId: RecordIdInput): Promise<SdkOrganizationMember | null> {
122
+ function getMembership(userId: RecordIdInput, organizationId: RecordIdInput) {
95
123
  const userRef = ensureRecordId(userId, TABLES.USER)
96
124
  const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
97
- const record = (await this.findAll({ in: userRef, out: organizationRef }, { limit: 1 })).at(0)
98
- return record ? this.toPublic(record) : null
125
+ return findMembershipRecords({ in: userRef, out: organizationRef }, { limit: 1 }).pipe(
126
+ Effect.mapError((cause) => toOrganizationMemberServiceError('getMembership', cause)),
127
+ Effect.map((records) => records.at(0) ?? null),
128
+ Effect.map((record) => (record ? toPublic(record) : null)),
129
+ )
99
130
  }
100
131
 
101
- async isMember(userId: RecordIdInput, organizationId: RecordIdInput): Promise<boolean> {
102
- const userRef = ensureRecordId(userId, TABLES.USER)
103
- const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
104
- const existing = await this.findAll({ in: userRef, out: organizationRef }, { limit: 1 })
105
- return existing.length > 0
132
+ function isMember(userId: RecordIdInput, organizationId: RecordIdInput) {
133
+ return getMembership(userId, organizationId).pipe(Effect.map((membership) => membership !== null))
106
134
  }
107
135
 
108
- async removeMembership(params: { userId: RecordIdInput; organizationId: RecordIdInput }): Promise<void> {
136
+ function removeMembership(params: { userId: RecordIdInput; organizationId: RecordIdInput }) {
109
137
  const userRef = ensureRecordId(params.userId, TABLES.USER)
110
138
  const organizationRef = ensureRecordId(params.organizationId, TABLES.ORGANIZATION)
111
- const existing = await this.findAll({ in: userRef, out: organizationRef }, { limit: 1 })
112
- const record = existing.at(0)
113
- if (!record) {
114
- return
115
- }
139
+ return Effect.gen(function* () {
140
+ const records = yield* findMembershipRecords({ in: userRef, out: organizationRef }, { limit: 1 }).pipe(
141
+ Effect.mapError((cause) => toOrganizationMemberServiceError('removeMembership.findExisting', cause)),
142
+ )
143
+ const record = records.at(0)
144
+ if (!record) {
145
+ return
146
+ }
147
+
148
+ yield* db
149
+ .deleteById(TABLES.ORGANIZATION_MEMBER, record.id)
150
+ .pipe(Effect.mapError((cause) => toOrganizationMemberServiceError('removeMembership.delete', cause)))
151
+ })
152
+ }
116
153
 
117
- await this.delete(record.id)
154
+ return {
155
+ addMembership,
156
+ listMembershipsForOrganization,
157
+ listMembershipsForUser,
158
+ getMembership,
159
+ isMember,
160
+ removeMembership,
118
161
  }
119
162
  }
120
163
 
121
- export const organizationMemberService = new OrganizationMemberService()
164
+ export class OrganizationMemberServiceTag extends Context.Service<
165
+ OrganizationMemberServiceTag,
166
+ ReturnType<typeof makeOrganizationMemberService>
167
+ >()('OrganizationMemberService') {}
168
+
169
+ export const OrganizationMemberServiceLive = Layer.effect(
170
+ OrganizationMemberServiceTag,
171
+ Effect.gen(function* () {
172
+ const db = yield* DatabaseServiceTag
173
+ return makeOrganizationMemberService(db)
174
+ }),
175
+ )