@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,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
+ >()('@lota-sdk/core/NodeWorkspaceService') {}
198
+
199
+ export const NodeWorkspaceServiceLive = Layer.sync(NodeWorkspaceServiceTag, () => makeNodeWorkspaceService())
@@ -1,4 +1,5 @@
1
1
  import type { NotificationSeverity } from '@lota-sdk/shared'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
3
4
  export interface NotificationPayload {
4
5
  organizationId: string
@@ -16,24 +17,36 @@ export interface NotificationService {
16
17
  notify(payload: NotificationPayload): Promise<void>
17
18
  remind(payload: NotificationPayload): Promise<void>
18
19
  escalate(payload: NotificationPayload): Promise<void>
20
+ notifyEffect?(payload: NotificationPayload): Effect.Effect<void, never>
21
+ remindEffect?(payload: NotificationPayload): Effect.Effect<void, never>
22
+ escalateEffect?(payload: NotificationPayload): Effect.Effect<void, never>
19
23
  }
20
24
 
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
25
+ export function makeNotificationService(): Required<NotificationService> {
26
+ return {
27
+ notify(_payload: NotificationPayload): Promise<void> {
28
+ return Promise.resolve()
29
+ },
30
+ remind(_payload: NotificationPayload): Promise<void> {
31
+ return Promise.resolve()
32
+ },
33
+ escalate(_payload: NotificationPayload): Promise<void> {
34
+ return Promise.resolve()
35
+ },
36
+ notifyEffect(_payload: NotificationPayload): Effect.Effect<void, never> {
37
+ return Effect.void
38
+ },
39
+ remindEffect(_payload: NotificationPayload): Effect.Effect<void, never> {
40
+ return Effect.void
41
+ },
42
+ escalateEffect(_payload: NotificationPayload): Effect.Effect<void, never> {
43
+ return Effect.void
44
+ },
45
+ }
31
46
  }
32
47
 
33
- export function getNotificationService(): NotificationService {
34
- if (_notificationService === null) {
35
- throw new Error('Notification service is not configured.')
36
- }
48
+ export class NotificationServiceTag extends Context.Service<NotificationServiceTag, NotificationService>()(
49
+ '@lota-sdk/core/NotificationService',
50
+ ) {}
37
51
 
38
- return _notificationService
39
- }
52
+ export const NotificationServiceLive = Layer.sync(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
+ >()('@lota-sdk/core/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
+ )