@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
@@ -13,17 +13,24 @@ import {
13
13
  } from '@lota-sdk/shared'
14
14
  import type { CreateProjectWithPlanResultData, ExecutionPlanArgs } from '@lota-sdk/shared'
15
15
  import { tool } from 'ai'
16
+ import { Effect } from 'effect'
16
17
 
18
+ import { serverLogger } from '../config/logger'
17
19
  import type { RecordIdRef } from '../db/record-id'
18
20
  import { recordIdToString } from '../db/record-id'
19
21
  import { TABLES } from '../db/tables'
20
- import { executionPlanService } from '../services/execution-plan.service'
21
- import { threadService } from '../services/thread.service'
22
+ import { BadRequestError, ConflictError, ForbiddenError } from '../effect/errors'
23
+ import { toValidationError } from '../effect/zod'
24
+ import type { makeExecutionPlanService } from '../services/execution-plan/execution-plan.service'
25
+ import type { makeThreadService } from '../services/thread/thread.service'
22
26
 
23
- type ExecutionPlanThreadService = Pick<typeof threadService, 'createThread' | 'deleteThread' | 'getThread'>
27
+ type ExecutionPlanThreadService = Pick<
28
+ ReturnType<typeof makeThreadService>,
29
+ 'createThread' | 'deleteThread' | 'getThread'
30
+ >
24
31
 
25
32
  type ExecutionPlanExecutionPlanService = Pick<
26
- typeof executionPlanService,
33
+ ReturnType<typeof makeExecutionPlanService>,
27
34
  | 'createPlan'
28
35
  | 'replacePlan'
29
36
  | 'resumeRun'
@@ -38,207 +45,259 @@ export function createExecutionPlanTool(params: {
38
45
  userId: RecordIdRef
39
46
  threadId: RecordIdRef
40
47
  agentId: string
41
- executionPlanService?: ExecutionPlanExecutionPlanService
42
- threadService?: ExecutionPlanThreadService
48
+ executionPlanService: ExecutionPlanExecutionPlanService
49
+ threadService: ExecutionPlanThreadService
43
50
  onPlanChanged?: () => void
44
51
  validateInlinePlan?: (draft: ReturnType<typeof expandAgentPlanDraft>) => void
45
52
  }) {
46
- const resolvedEpService = params.executionPlanService ?? executionPlanService
47
- const resolvedWsService = params.threadService ?? threadService
48
-
49
53
  return tool({
50
54
  description:
51
55
  'Manage execution plans. Actions: create (inline, 1-2 nodes), create-project (dedicated project thread, 3+ nodes), replace (swap active plan), resume (resume interrupted plan), update-node (submit result for a specific node).',
52
56
  inputSchema: ExecutionPlanArgsSchema,
53
- execute: async (input) => {
54
- const parsed = parseExecutionPlanArgs(input)
55
- let result: unknown
56
-
57
- switch (parsed.action) {
58
- case 'create': {
59
- const draft = extractAgentPlanDraft(parsed)
60
- params.validateInlinePlan?.(draft)
61
- const targetThreadId = parsed.targetThreadId ?? params.threadId
62
- const isCrossThreadTarget =
63
- recordIdToString(targetThreadId, TABLES.THREAD) !== recordIdToString(params.threadId, TABLES.THREAD)
64
- result = await resolvedEpService.createPlan({
65
- organizationId: params.orgId,
66
- threadId: targetThreadId,
67
- ...(isCrossThreadTarget ? { sourceThreadId: params.threadId } : {}),
68
- leadAgentId: params.agentId,
69
- createdByAgentId: params.agentId,
70
- requireApproval: parsed.requireApproval ?? isCrossThreadTarget,
71
- input: draft,
72
- })
73
- break
74
- }
75
-
76
- case 'create-project': {
77
- const draft = extractAgentPlanDraft(parsed)
78
- const targetThread = parsed.targetThreadId
79
- ? await resolvedWsService.getThread(parsed.targetThreadId)
80
- : await (() => {
81
- if (!parsed.projectTitle) {
82
- throw new Error('projectTitle is required when action is "create-project".')
57
+ execute: (input) =>
58
+ Effect.runPromise(
59
+ Effect.gen(function* () {
60
+ const parsed = yield* parseExecutionPlanArgsEffect(input)
61
+
62
+ switch (parsed.action) {
63
+ case 'create': {
64
+ const draft = yield* extractAgentPlanDraftEffect(parsed)
65
+ params.validateInlinePlan?.(draft)
66
+ const targetThreadId = parsed.targetThreadId ?? params.threadId
67
+ const isCrossThreadTarget =
68
+ recordIdToString(targetThreadId, TABLES.THREAD) !== recordIdToString(params.threadId, TABLES.THREAD)
69
+ const result = yield* params.executionPlanService.createPlan({
70
+ organizationId: params.orgId,
71
+ threadId: targetThreadId,
72
+ ...(isCrossThreadTarget ? { sourceThreadId: params.threadId } : {}),
73
+ leadAgentId: params.agentId,
74
+ createdByAgentId: params.agentId,
75
+ requireApproval: parsed.requireApproval ?? isCrossThreadTarget,
76
+ input: draft,
77
+ })
78
+ params.onPlanChanged?.()
79
+ return result
80
+ }
81
+
82
+ case 'create-project': {
83
+ const draft = yield* extractAgentPlanDraftEffect(parsed)
84
+ let createdThreadId: string | null = null
85
+
86
+ const cleanupLeakedThread = (threadId: string) =>
87
+ params.threadService.deleteThread(threadId).pipe(
88
+ Effect.catch((cleanupError: unknown) =>
89
+ Effect.sync(() => {
90
+ serverLogger.warn`Failed to clean up leaked execution-plan project thread ${threadId}: ${cleanupError}`
91
+ }),
92
+ ),
93
+ )
94
+
95
+ const projectEffect = Effect.gen(function* () {
96
+ const projectTitle = parsed.projectTitle
97
+ const targetThreadId = parsed.targetThreadId
98
+ const targetThread = targetThreadId
99
+ ? yield* params.threadService.getThread(targetThreadId)
100
+ : yield* (() => {
101
+ if (!projectTitle) {
102
+ return new BadRequestError({
103
+ message: 'projectTitle is required when action is "create-project".',
104
+ })
105
+ }
106
+
107
+ return params.threadService.createThread({
108
+ userId: params.userId,
109
+ organizationId: params.orgId,
110
+ title: projectTitle,
111
+ type: 'group',
112
+ })
113
+ })()
114
+
115
+ if (!parsed.targetThreadId) {
116
+ createdThreadId = targetThread.id
117
+ }
118
+
119
+ if (targetThread.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
120
+ return yield* new ForbiddenError({ message: 'Target thread belongs to a different organization.' })
121
+ }
122
+ if (targetThread.userId !== recordIdToString(params.userId, TABLES.USER)) {
123
+ return yield* new ForbiddenError({ message: 'Target thread belongs to a different user.' })
124
+ }
125
+
126
+ const existingPlans = yield* params.executionPlanService.getActivePlansForThread(targetThread.id)
127
+ if (targetThread.type !== 'thread' && existingPlans.length > 0) {
128
+ return yield* new ConflictError({
129
+ message:
130
+ 'This thread already has an active execution plan. Use action "replace" or target a core thread.',
131
+ })
83
132
  }
84
133
 
85
- return resolvedWsService.createThread({
86
- userId: params.userId,
134
+ const created = yield* params.executionPlanService.createPlan({
87
135
  organizationId: params.orgId,
88
- title: parsed.projectTitle,
89
- type: 'group',
136
+ threadId: targetThread.id,
137
+ sourceThreadId: params.threadId,
138
+ leadAgentId: params.agentId,
139
+ createdByAgentId: params.agentId,
140
+ requireApproval: parsed.requireApproval ?? true,
141
+ input: draft,
90
142
  })
91
- })()
92
-
93
- if (targetThread.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
94
- throw new Error('Target thread belongs to a different organization.')
95
- }
96
- if (targetThread.userId !== recordIdToString(params.userId, TABLES.USER)) {
97
- throw new Error('Target thread belongs to a different user.')
98
- }
99
143
 
100
- const existingPlans = await resolvedEpService.getActivePlansForThread(targetThread.id)
101
- if (targetThread.type !== 'thread' && existingPlans.length > 0) {
102
- throw new Error(
103
- 'This thread already has an active execution plan. Use action "replace" or target a core thread.',
104
- )
105
- }
144
+ const result = {
145
+ ...created,
146
+ runId: created.plan?.runId ?? '',
147
+ threadId: targetThread.id,
148
+ threadTitle: targetThread.title,
149
+ createdThread: createdThreadId !== null,
150
+ } satisfies CreateProjectWithPlanResultData
151
+ params.onPlanChanged?.()
152
+ return result
153
+ }).pipe(Effect.tapError(() => (createdThreadId ? cleanupLeakedThread(createdThreadId) : Effect.void)))
106
154
 
107
- const createdThread = !parsed.targetThreadId
108
- try {
109
- const created = await resolvedEpService.createPlan({
110
- organizationId: params.orgId,
111
- threadId: targetThread.id,
112
- sourceThreadId: params.threadId,
113
- leadAgentId: params.agentId,
114
- createdByAgentId: params.agentId,
115
- requireApproval: parsed.requireApproval ?? true,
116
- input: draft,
117
- })
118
- result = {
119
- ...created,
120
- runId: created.plan?.runId ?? '',
121
- threadId: targetThread.id,
122
- threadTitle: targetThread.title,
123
- createdThread,
124
- } satisfies CreateProjectWithPlanResultData
125
- } catch (error) {
126
- if (createdThread) {
127
- await resolvedWsService.deleteThread(targetThread.id).catch(() => {})
155
+ return yield* projectEffect
128
156
  }
129
- throw error
130
- }
131
- break
132
- }
133
-
134
- case 'replace': {
135
- const draft = extractAgentPlanDraft(parsed)
136
- result = await resolvedEpService.replacePlan({
137
- organizationId: params.orgId,
138
- threadId: params.threadId,
139
- leadAgentId: params.agentId,
140
- createdByAgentId: params.agentId,
141
- input: { runId: parsed.runId, reason: parsed.reason, requireApproval: parsed.requireApproval, ...draft },
142
- })
143
- break
144
- }
145
157
 
146
- case 'resume':
147
- result = await resolvedEpService.resumeRun({
148
- threadId: params.threadId,
149
- emittedBy: params.agentId,
150
- input: { runId: parsed.runId },
151
- })
152
- break
158
+ case 'replace': {
159
+ const draft = yield* extractAgentPlanDraftEffect(parsed)
160
+ const result = yield* params.executionPlanService.replacePlan({
161
+ organizationId: params.orgId,
162
+ threadId: params.threadId,
163
+ leadAgentId: params.agentId,
164
+ createdByAgentId: params.agentId,
165
+ input: {
166
+ runId: parsed.runId,
167
+ reason: parsed.reason,
168
+ requireApproval: parsed.requireApproval,
169
+ ...draft,
170
+ },
171
+ })
172
+ params.onPlanChanged?.()
173
+ return result
174
+ }
153
175
 
154
- case 'update-node':
155
- result = await resolvedEpService.submitNodeResult({
156
- threadId: params.threadId,
157
- emittedBy: params.agentId,
158
- input: {
159
- runId: parsed.runId,
160
- nodeId: parsed.node.id,
161
- notes: parsed.node.latestNotes,
162
- artifacts: parsed.node.deliverables ?? [],
163
- },
164
- })
165
- break
166
- }
176
+ case 'resume': {
177
+ const result = yield* params.executionPlanService.resumeRun({
178
+ threadId: params.threadId,
179
+ emittedBy: params.agentId,
180
+ input: { runId: parsed.runId },
181
+ })
182
+ params.onPlanChanged?.()
183
+ return result
184
+ }
167
185
 
168
- params.onPlanChanged?.()
169
- return result
170
- },
186
+ case 'update-node': {
187
+ const result = yield* params.executionPlanService.submitNodeResult({
188
+ threadId: params.threadId,
189
+ emittedBy: params.agentId,
190
+ input: {
191
+ runId: parsed.runId,
192
+ nodeId: parsed.node.id,
193
+ notes: parsed.node.latestNotes,
194
+ artifacts: parsed.node.deliverables ?? [],
195
+ },
196
+ })
197
+ params.onPlanChanged?.()
198
+ return result
199
+ }
200
+ }
201
+ }),
202
+ ),
171
203
  })
172
204
  }
173
205
 
174
- function parseExecutionPlanArgs(input: unknown): ExecutionPlanArgs {
175
- const parsed = ExecutionPlanArgsSchema.parse(input)
176
-
177
- switch (parsed.action) {
178
- case 'create':
179
- return ExecutionPlanCreateArgsSchema.parse(parsed)
180
- case 'create-project':
181
- return ExecutionPlanCreateProjectArgsSchema.parse(parsed)
182
- case 'replace':
183
- return ExecutionPlanReplaceArgsSchema.parse(parsed)
184
- case 'resume':
185
- return ExecutionPlanResumeArgsSchema.parse(parsed)
186
- case 'update-node':
187
- return ExecutionPlanUpdateNodeArgsSchema.parse(parsed)
188
- }
206
+ function parseExecutionPlanArgsEffect(input: unknown) {
207
+ return Effect.gen(function* () {
208
+ const parsedArgs = ExecutionPlanArgsSchema.safeParse(input)
209
+ if (!parsedArgs.success) {
210
+ return yield* toValidationError(parsedArgs.error, 'Invalid execution plan tool input')
211
+ }
212
+ const parsed = parsedArgs.data
213
+
214
+ const parseAction = <T>(result: { success: true; data: T } | { success: false; error: unknown }) =>
215
+ result.success
216
+ ? Effect.succeed(result.data)
217
+ : Effect.fail(toValidationError(result.error, 'Invalid execution plan action input'))
218
+
219
+ switch (parsed.action) {
220
+ case 'create':
221
+ return yield* parseAction(ExecutionPlanCreateArgsSchema.safeParse(parsed))
222
+ case 'create-project':
223
+ return yield* parseAction(ExecutionPlanCreateProjectArgsSchema.safeParse(parsed))
224
+ case 'replace':
225
+ return yield* parseAction(ExecutionPlanReplaceArgsSchema.safeParse(parsed))
226
+ case 'resume':
227
+ return yield* parseAction(ExecutionPlanResumeArgsSchema.safeParse(parsed))
228
+ case 'update-node':
229
+ return yield* parseAction(ExecutionPlanUpdateNodeArgsSchema.safeParse(parsed))
230
+ }
231
+ })
189
232
  }
190
233
 
191
- function extractAgentPlanDraft(input: Extract<ExecutionPlanArgs, { action: 'create' | 'create-project' | 'replace' }>) {
192
- return expandAgentPlanDraft(
193
- AgentPlanDraftSchema.parse({
234
+ function extractAgentPlanDraftEffect(
235
+ input: Extract<ExecutionPlanArgs, { action: 'create' | 'create-project' | 'replace' }>,
236
+ ) {
237
+ return Effect.gen(function* () {
238
+ const parsedDraft = AgentPlanDraftSchema.safeParse({
194
239
  title: input.title,
195
240
  objective: input.objective,
196
241
  nodes: input.nodes,
197
242
  edges: input.edges,
198
- }),
199
- )
243
+ })
244
+ if (!parsedDraft.success) {
245
+ return yield* toValidationError(parsedDraft.error, 'Invalid execution plan draft')
246
+ }
247
+ return expandAgentPlanDraft(parsedDraft.data)
248
+ })
200
249
  }
201
250
 
202
- export function createExecutionPlanQueryTool(params: { threadId: RecordIdRef }) {
251
+ export function createExecutionPlanQueryTool(params: {
252
+ threadId: RecordIdRef
253
+ executionPlanService: Pick<ExecutionPlanExecutionPlanService, 'listActivePlanSummaries' | 'getActivePlanToolResult'>
254
+ }) {
203
255
  return tool({
204
256
  description:
205
257
  'Query execution plans. Omit runId to list all active plans. Provide runId to load a specific plan run.',
206
258
  inputSchema: ExecutionPlanQueryArgsSchema,
207
- execute: async (input) => {
208
- if (!input.runId) {
209
- return await executionPlanService.listActivePlanSummaries(params.threadId)
210
- }
211
- return await executionPlanService.getActivePlanToolResult({
212
- threadId: params.threadId,
213
- runId: input.runId,
214
- includeEvents: input.includeEvents,
215
- includeArtifacts: input.includeArtifacts,
216
- includeApprovals: input.includeApprovals,
217
- includeCheckpoints: input.includeCheckpoints,
218
- includeValidationIssues: input.includeValidationIssues,
219
- })
220
- },
259
+ execute: (input) =>
260
+ Effect.runPromise(
261
+ Effect.gen(function* () {
262
+ if (!input.runId) {
263
+ return yield* params.executionPlanService.listActivePlanSummaries(params.threadId)
264
+ }
265
+ return yield* params.executionPlanService.getActivePlanToolResult({
266
+ threadId: params.threadId,
267
+ runId: input.runId,
268
+ includeEvents: input.includeEvents,
269
+ includeArtifacts: input.includeArtifacts,
270
+ includeApprovals: input.includeApprovals,
271
+ includeCheckpoints: input.includeCheckpoints,
272
+ includeValidationIssues: input.includeValidationIssues,
273
+ })
274
+ }),
275
+ ),
221
276
  })
222
277
  }
223
278
 
224
279
  export function createSubmitExecutionNodeResultTool(params: {
225
280
  threadId: RecordIdRef
226
281
  agentId: string
282
+ executionPlanService: Pick<ExecutionPlanExecutionPlanService, 'submitNodeResult'>
227
283
  onPlanChanged?: () => void
228
284
  }) {
229
285
  return tool({
230
286
  description:
231
287
  'Submit the result for the currently running execution node. The executor validates outputs, artifacts, and completion checks before advancing the run.',
232
288
  inputSchema: SubmitExecutionNodeResultArgsSchema,
233
- execute: async (input) => {
234
- const result = await executionPlanService.submitNodeResult({
235
- threadId: params.threadId,
236
- emittedBy: params.agentId,
237
- input,
238
- })
239
- params.onPlanChanged?.()
240
- return result
241
- },
289
+ execute: (input) =>
290
+ Effect.runPromise(
291
+ Effect.gen(function* () {
292
+ const result = yield* params.executionPlanService.submitNodeResult({
293
+ threadId: params.threadId,
294
+ emittedBy: params.agentId,
295
+ input,
296
+ })
297
+ params.onPlanChanged?.()
298
+ return result
299
+ }),
300
+ ),
242
301
  toModelOutput: ({ output }) => {
243
302
  const result = getLatestExecutionPlanResult(output)
244
303
  const summary = result?.message?.trim()
@@ -1,24 +1,21 @@
1
1
  import { tool } from 'ai'
2
+ import { Effect } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
5
  import type { ToolDefinition } from '../ai/definitions'
5
6
  import { withTimeout } from '../utils/async'
7
+ import { nowIsoDateTimeString } from '../utils/date-time'
6
8
  import { readStringField, truncateOptionalText } from '../utils/string'
7
9
  import { getFirecrawlClient } from './firecrawl-client'
8
10
  import type { WebCitation } from './tool-contracts'
11
+ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
9
12
 
10
- const TOOL_TIMEOUT_MS = 30_000
11
13
  const FormatSchema = z.enum(['markdown', 'html', 'rawHtml', 'links', 'images', 'screenshot', 'summary'])
12
14
  const MAX_MARKDOWN_CHARS = 6_000
13
15
  const MAX_SUMMARY_CHARS = 1_200
14
16
  const MAX_LINKS = 25
15
17
  const MAX_IMAGES = 10
16
18
 
17
- function toRecord(value: unknown): Record<string, unknown> | null {
18
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null
19
- return value as Record<string, unknown>
20
- }
21
-
22
19
  function readStringList(record: Record<string, unknown>, key: string, maxItems: number): string[] {
23
20
  const value = record[key]
24
21
  if (!Array.isArray(value)) return []
@@ -91,7 +88,7 @@ function buildFetchCitations(url: string, document: unknown): WebCitation[] {
91
88
  }
92
89
  }
93
90
 
94
- return [{ source: 'web', sourceId, retrievedAt: new Date().toISOString() }]
91
+ return [{ source: 'web', sourceId, retrievedAt: nowIsoDateTimeString() }]
95
92
  }
96
93
 
97
94
  export const fetchWebpageTool = {
@@ -107,7 +104,7 @@ export const fetchWebpageTool = {
107
104
  maxAge: z.number().int().min(1).optional(),
108
105
  })
109
106
  .strict(),
110
- execute: async ({
107
+ execute: ({
111
108
  url,
112
109
  formats,
113
110
  onlyMainContent,
@@ -118,17 +115,24 @@ export const fetchWebpageTool = {
118
115
  onlyMainContent?: boolean
119
116
  maxAge?: number
120
117
  }) => {
121
- const result = await withTimeout(
122
- getFirecrawlClient().scrape(url, {
123
- formats: formats?.length ? formats : ['markdown'],
124
- onlyMainContent: onlyMainContent ?? true,
125
- maxAge,
118
+ const firecrawl = getFirecrawlClient()
119
+
120
+ return Effect.runPromise(
121
+ Effect.gen(function* () {
122
+ const result = yield* Effect.tryPromise(() =>
123
+ withTimeout(
124
+ firecrawl.scrape(url, {
125
+ formats: formats?.length ? formats : ['markdown'],
126
+ onlyMainContent: onlyMainContent ?? true,
127
+ maxAge,
128
+ }),
129
+ WEB_TOOL_TIMEOUT_MS,
130
+ 'Webpage fetch',
131
+ ),
132
+ )
133
+ return { url, document: summarizeDocument(url, result), citations: buildFetchCitations(url, result) }
126
134
  }),
127
- TOOL_TIMEOUT_MS,
128
- 'Webpage fetch',
129
135
  )
130
-
131
- return { url, document: summarizeDocument(url, result), citations: buildFetchCitations(url, result) }
132
136
  },
133
137
  }),
134
138
  } as const satisfies ToolDefinition<void>
@@ -1,12 +1,22 @@
1
1
  import Firecrawl from '@mendable/firecrawl-js'
2
+ import { Context, Effect, Layer } from 'effect'
2
3
 
3
- import { getRuntimeConfig } from '../runtime/runtime-config'
4
+ import { getCurrentRuntime } from '../effect/runtime-ref'
5
+ import { RuntimeConfigServiceTag } from '../effect/services'
4
6
 
5
- let _firecrawlClient: Firecrawl | undefined
7
+ export class FirecrawlTag extends Context.Service<FirecrawlTag, Firecrawl>()('Firecrawl') {}
8
+
9
+ export const FirecrawlLive = Layer.effect(
10
+ FirecrawlTag,
11
+ Effect.gen(function* () {
12
+ const config = yield* RuntimeConfigServiceTag
13
+ return new Firecrawl({
14
+ apiKey: config.firecrawl.apiKey,
15
+ ...(config.firecrawl.apiBaseUrl ? { apiUrl: config.firecrawl.apiBaseUrl } : {}),
16
+ })
17
+ }),
18
+ )
6
19
 
7
20
  export function getFirecrawlClient(): Firecrawl {
8
- if (!_firecrawlClient) {
9
- _firecrawlClient = new Firecrawl({ apiKey: getRuntimeConfig().firecrawl.apiKey })
10
- }
11
- return _firecrawlClient
21
+ return getCurrentRuntime().runSync(Effect.service(FirecrawlTag))
12
22
  }
@@ -1,5 +1,6 @@
1
1
  export * from './execution-plan.tool'
2
2
  export * from './fetch-webpage.tool'
3
+ export * from './firecrawl-client'
3
4
  export * from './memory-block.tool'
4
5
  export * from './plan-approval.tool'
5
6
  export * from './read-file-parts.tool'
@@ -1,4 +1,5 @@
1
1
  import { tool } from 'ai'
2
+ import { Effect } from 'effect'
2
3
  import { z } from 'zod'
3
4
 
4
5
  import type { RecordIdRef } from '../db/record-id'
@@ -7,18 +8,22 @@ import {
7
8
  normalizeMemoryBlockEntry,
8
9
  prepareMemoryBlockAppend,
9
10
  validateMemoryBlockEntry,
10
- } from '../runtime/memory-block'
11
- import { threadService } from '../services/thread.service'
11
+ } from '../runtime/memory/memory-block'
12
+ import type { makeThreadService } from '../services/thread/thread.service'
12
13
  import { safeEnqueue } from '../utils/async'
13
14
 
15
+ type MemoryBlockThreadService = Pick<ReturnType<typeof makeThreadService>, 'appendMemoryBlock'>
16
+
14
17
  export function createMemoryBlockTool({
15
18
  threadId,
16
19
  agentLabel,
20
+ threadService,
17
21
  getCurrentBlock,
18
22
  onAppend,
19
23
  }: {
20
24
  threadId: RecordIdRef
21
25
  agentLabel: string
26
+ threadService: MemoryBlockThreadService
22
27
  getCurrentBlock?: () => string
23
28
  onAppend?: (value: string) => void
24
29
  }) {
@@ -42,10 +47,13 @@ export function createMemoryBlockTool({
42
47
  onAppend?.(prepared.optimisticBlock)
43
48
 
44
49
  void safeEnqueue(
45
- async () => {
46
- const updated = await threadService.appendMemoryBlock(threadId, prepared.formatted)
47
- onAppend?.(updated)
48
- },
50
+ () =>
51
+ Effect.runPromise(
52
+ Effect.gen(function* () {
53
+ const updated = yield* threadService.appendMemoryBlock(threadId, prepared.formatted)
54
+ onAppend?.(updated)
55
+ }),
56
+ ),
49
57
  { operationName: 'append memory block entry', logPrefix: 'Background memoryBlockAppend task failed' },
50
58
  )
51
59