@lota-sdk/core 0.4.9 → 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 (158) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. package/src/runtime/agent-types.ts +0 -1
@@ -107,11 +107,14 @@ export function makeAttachmentStorageService(config: ResolvedLotaRuntimeConfig)
107
107
  storageKey: string
108
108
  orgId: string
109
109
  userId: string
110
- }): void {
110
+ }): Effect.Effect<void, ForbiddenError> {
111
111
  const storagePrefix = buildUploadStoragePrefix({ orgId, userId })
112
112
  if (!storageKey.startsWith(storagePrefix)) {
113
- throw new ForbiddenError({ message: 'Upload does not belong to the current organization/user scope.' })
113
+ return Effect.fail(
114
+ new ForbiddenError({ message: 'Upload does not belong to the current organization/user scope.' }),
115
+ )
114
116
  }
117
+ return Effect.void
115
118
  }
116
119
 
117
120
  function writeOrganizationDocumentEffect({
@@ -284,7 +287,7 @@ export function makeAttachmentStorageService(config: ResolvedLotaRuntimeConfig)
284
287
  pagesPerPart?: number
285
288
  }) {
286
289
  return Effect.gen(function* () {
287
- assertUploadOwnership({ storageKey: upload.storageKey, orgId, userId })
290
+ yield* assertUploadOwnership({ storageKey: upload.storageKey, orgId, userId })
288
291
 
289
292
  const parsed = yield* extractStoredAttachmentPagesEffect({
290
293
  storageKey: upload.storageKey,
@@ -437,7 +440,7 @@ export function makeAttachmentStorageService(config: ResolvedLotaRuntimeConfig)
437
440
  export class AttachmentStorageServiceTag extends Context.Service<
438
441
  AttachmentStorageServiceTag,
439
442
  ReturnType<typeof makeAttachmentStorageService>
440
- >()('AttachmentStorageService') {}
443
+ >()('@lota-sdk/core/AttachmentStorageService') {}
441
444
 
442
445
  export const AttachmentStorageServiceLive = Layer.effect(
443
446
  AttachmentStorageServiceTag,
@@ -86,7 +86,7 @@ export function makeGeneratedDocumentStorageService(config: ResolvedLotaRuntimeC
86
86
  export class GeneratedDocumentStorageServiceTag extends Context.Service<
87
87
  GeneratedDocumentStorageServiceTag,
88
88
  ReturnType<typeof makeGeneratedDocumentStorageService>
89
- >()('GeneratedDocumentStorageService') {}
89
+ >()('@lota-sdk/core/GeneratedDocumentStorageService') {}
90
90
 
91
91
  export const GeneratedDocumentStorageServiceLive = Layer.effect(
92
92
  GeneratedDocumentStorageServiceTag,
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -6,7 +7,6 @@ import {
6
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
8
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
8
9
  } from '../config/model-constants'
9
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
10
10
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  const CONTEXT_COMPACTION_PROMPT = `<agent-instructions>
@@ -1,4 +1,4 @@
1
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
 
3
3
  interface HelperAgentOptionOverrides {
4
4
  instructions?: string
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -6,7 +7,6 @@ import {
6
7
  OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
7
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
9
  } from '../config/model-constants'
9
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
10
10
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  export const MEMORY_RERANKER_PROMPT = `<agent-instructions>
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -6,7 +7,6 @@ import {
6
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
8
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
8
9
  } from '../config/model-constants'
9
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
10
10
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  export const ORG_MEMORY_PROMPT = `<agent-instructions>
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -7,7 +8,6 @@ import {
7
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
9
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
9
10
  } from '../config/model-constants'
10
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
11
11
  import { resolveHelperAgentOptions } from './helper-agent-options'
12
12
 
13
13
  const RECENT_ACTIVITY_TITLE_MAX_TOKENS = 256
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -6,7 +7,6 @@ import {
6
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
8
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
8
9
  } from '../config/model-constants'
9
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
10
10
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  const REGULAR_CHAT_MEMORY_DIGEST_MAX_TOKENS = 8_192
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
  import { z } from 'zod'
3
4
 
@@ -7,7 +8,6 @@ import {
7
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
9
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
9
10
  } from '../config/model-constants'
10
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
11
11
  import { resolveHelperAgentOptions } from './helper-agent-options'
12
12
 
13
13
  const SKILL_EXTRACTOR_MAX_TOKENS = 8_192
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
  import { z } from 'zod'
3
4
 
@@ -7,7 +8,6 @@ import {
7
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
9
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
9
10
  } from '../config/model-constants'
10
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
11
11
  import { resolveHelperAgentOptions } from './helper-agent-options'
12
12
 
13
13
  const SKILL_MANAGER_MAX_TOKENS = 4_096
@@ -1,3 +1,4 @@
1
+ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
1
2
  import { ToolLoopAgent } from 'ai'
2
3
 
3
4
  import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
@@ -6,7 +7,6 @@ import {
6
7
  OPENROUTER_FAST_REASONING_MODEL_ID,
7
8
  OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
8
9
  } from '../config/model-constants'
9
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
10
10
  import { resolveHelperAgentOptions } from './helper-agent-options'
11
11
 
12
12
  const THREAD_TITLE_MAX_TOKENS = 512
@@ -24,22 +24,31 @@ import { toValidationError } from '../effect/zod'
24
24
  import type { makeExecutionPlanService } from '../services/execution-plan/execution-plan.service'
25
25
  import type { makeThreadService } from '../services/thread/thread.service'
26
26
 
27
- type ExecutionPlanThreadService = Pick<
28
- ReturnType<typeof makeThreadService>,
29
- 'createThread' | 'deleteThread' | 'getThread'
27
+ type NoContextService<TService> = {
28
+ [K in keyof TService]: TService[K] extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, unknown>
29
+ ? (...args: TArgs) => Effect.Effect<A, E, never>
30
+ : TService[K]
31
+ }
32
+
33
+ type ExecutionPlanThreadService = NoContextService<
34
+ Pick<ReturnType<typeof makeThreadService>, 'createThread' | 'deleteThread' | 'getThread'>
30
35
  >
31
36
 
32
- type ExecutionPlanExecutionPlanService = Pick<
33
- ReturnType<typeof makeExecutionPlanService>,
34
- | 'createPlan'
35
- | 'replacePlan'
36
- | 'resumeRun'
37
- | 'submitNodeResult'
38
- | 'listActivePlanSummaries'
39
- | 'getActivePlanToolResult'
40
- | 'getActivePlansForThread'
37
+ type ExecutionPlanExecutionPlanService = NoContextService<
38
+ Pick<
39
+ ReturnType<typeof makeExecutionPlanService>,
40
+ | 'createPlan'
41
+ | 'replacePlan'
42
+ | 'resumeRun'
43
+ | 'submitNodeResult'
44
+ | 'listActivePlanSummaries'
45
+ | 'getActivePlanToolResult'
46
+ | 'getActivePlansForThread'
47
+ >
41
48
  >
42
49
 
50
+ type ExecutionPlanToolValidationError = ReturnType<typeof toValidationError>
51
+
43
52
  export function createExecutionPlanTool(params: {
44
53
  orgId: RecordIdRef
45
54
  userId: RecordIdRef
@@ -198,12 +207,14 @@ export function createExecutionPlanTool(params: {
198
207
  return result
199
208
  }
200
209
  }
201
- }),
210
+ }).pipe(Effect.withSpan('tool.executionPlan.execute')),
202
211
  ),
203
212
  })
204
213
  }
205
214
 
206
- function parseExecutionPlanArgsEffect(input: unknown) {
215
+ function parseExecutionPlanArgsEffect(
216
+ input: unknown,
217
+ ): Effect.Effect<ExecutionPlanArgs, ExecutionPlanToolValidationError> {
207
218
  return Effect.gen(function* () {
208
219
  const parsedArgs = ExecutionPlanArgsSchema.safeParse(input)
209
220
  if (!parsedArgs.success) {
@@ -233,7 +244,7 @@ function parseExecutionPlanArgsEffect(input: unknown) {
233
244
 
234
245
  function extractAgentPlanDraftEffect(
235
246
  input: Extract<ExecutionPlanArgs, { action: 'create' | 'create-project' | 'replace' }>,
236
- ) {
247
+ ): Effect.Effect<ReturnType<typeof expandAgentPlanDraft>, ExecutionPlanToolValidationError> {
237
248
  return Effect.gen(function* () {
238
249
  const parsedDraft = AgentPlanDraftSchema.safeParse({
239
250
  title: input.title,
@@ -271,7 +282,7 @@ export function createExecutionPlanQueryTool(params: {
271
282
  includeCheckpoints: input.includeCheckpoints,
272
283
  includeValidationIssues: input.includeValidationIssues,
273
284
  })
274
- }),
285
+ }).pipe(Effect.withSpan('tool.executionPlanQuery.execute')),
275
286
  ),
276
287
  })
277
288
  }
@@ -296,7 +307,7 @@ export function createSubmitExecutionNodeResultTool(params: {
296
307
  })
297
308
  params.onPlanChanged?.()
298
309
  return result
299
- }),
310
+ }).pipe(Effect.withSpan('tool.submitExecutionNodeResult.execute')),
300
311
  ),
301
312
  toModelOutput: ({ output }) => {
302
313
  const result = getLatestExecutionPlanResult(output)
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai'
2
- import { Effect } from 'effect'
2
+ import { Effect, Schema } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
@@ -10,6 +10,11 @@ import { getFirecrawlClient } from './firecrawl-client'
10
10
  import type { WebCitation } from './tool-contracts'
11
11
  import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
12
12
 
13
+ class FetchWebpageToolError extends Schema.TaggedErrorClass<FetchWebpageToolError>()(
14
+ '@lota-sdk/core/FetchWebpageToolError',
15
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
16
+ ) {}
17
+
13
18
  const FormatSchema = z.enum(['markdown', 'html', 'rawHtml', 'links', 'images', 'screenshot', 'summary'])
14
19
  const MAX_MARKDOWN_CHARS = 6_000
15
20
  const MAX_SUMMARY_CHARS = 1_200
@@ -119,19 +124,21 @@ export const fetchWebpageTool = {
119
124
 
120
125
  return Effect.runPromise(
121
126
  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
- )
127
+ const result = yield* Effect.tryPromise({
128
+ try: () =>
129
+ withTimeout(
130
+ firecrawl.scrape(url, {
131
+ formats: formats?.length ? formats : ['markdown'],
132
+ onlyMainContent: onlyMainContent ?? true,
133
+ maxAge,
134
+ }),
135
+ WEB_TOOL_TIMEOUT_MS,
136
+ 'Webpage fetch',
137
+ ),
138
+ catch: (cause) => new FetchWebpageToolError({ message: `Webpage fetch failed for ${url}.`, cause }),
139
+ })
133
140
  return { url, document: summarizeDocument(url, result), citations: buildFetchCitations(url, result) }
134
- }),
141
+ }).pipe(Effect.withSpan('tool.fetchWebpage.execute')),
135
142
  )
136
143
  },
137
144
  }),
@@ -1,10 +1,10 @@
1
1
  import Firecrawl from '@mendable/firecrawl-js'
2
2
  import { Context, Effect, Layer } from 'effect'
3
3
 
4
- import { getCurrentRuntime } from '../effect/runtime-ref'
4
+ import { resolveLotaService } from '../effect/runtime'
5
5
  import { RuntimeConfigServiceTag } from '../effect/services'
6
6
 
7
- export class FirecrawlTag extends Context.Service<FirecrawlTag, Firecrawl>()('Firecrawl') {}
7
+ export class FirecrawlTag extends Context.Service<FirecrawlTag, Firecrawl>()('@lota-sdk/core/Firecrawl') {}
8
8
 
9
9
  export const FirecrawlLive = Layer.effect(
10
10
  FirecrawlTag,
@@ -17,6 +17,16 @@ export const FirecrawlLive = Layer.effect(
17
17
  }),
18
18
  )
19
19
 
20
+ let currentFirecrawlClient: Firecrawl | null = null
21
+
22
+ export function configureFirecrawlClient(client: Firecrawl): void {
23
+ currentFirecrawlClient = client
24
+ }
25
+
26
+ export function clearFirecrawlClient(): void {
27
+ currentFirecrawlClient = null
28
+ }
29
+
20
30
  export function getFirecrawlClient(): Firecrawl {
21
- return getCurrentRuntime().runSync(Effect.service(FirecrawlTag))
31
+ return currentFirecrawlClient ?? resolveLotaService(FirecrawlTag)
22
32
  }
@@ -6,7 +6,15 @@ import type { RecordIdRef } from '../db/record-id'
6
6
  import type { makeExecutionPlanService } from '../services/execution-plan/execution-plan.service'
7
7
  import { toToolPlanSummary } from '../services/plan/plan-run-data'
8
8
 
9
- type PlanApprovalExecutionPlanService = Pick<ReturnType<typeof makeExecutionPlanService>, 'approvePlan' | 'rejectPlan'>
9
+ type NoContextService<TService> = {
10
+ [K in keyof TService]: TService[K] extends (...args: infer TArgs) => Effect.Effect<infer A, infer E, unknown>
11
+ ? (...args: TArgs) => Effect.Effect<A, E, never>
12
+ : TService[K]
13
+ }
14
+
15
+ type PlanApprovalExecutionPlanService = NoContextService<
16
+ Pick<ReturnType<typeof makeExecutionPlanService>, 'approvePlan' | 'rejectPlan'>
17
+ >
10
18
 
11
19
  export function createPlanApprovalTool(params: {
12
20
  orgId: RecordIdRef
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai'
2
- import { Effect } from 'effect'
2
+ import { Effect, Schema } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
@@ -10,6 +10,11 @@ import { getFirecrawlClient } from './firecrawl-client'
10
10
  import type { WebCitation } from './tool-contracts'
11
11
  import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
12
12
 
13
+ class SearchWebToolError extends Schema.TaggedErrorClass<SearchWebToolError>()('@lota-sdk/core/SearchWebToolError', {
14
+ message: Schema.String,
15
+ cause: Schema.optional(Schema.Defect),
16
+ }) {}
17
+
13
18
  const SourceSchema = z.enum(['web', 'news', 'images'])
14
19
  const MAX_RESULTS_PER_SOURCE = 4
15
20
  const MAX_SNIPPET_CHARS = 320
@@ -159,13 +164,15 @@ export const searchWebTool = {
159
164
 
160
165
  return Effect.runPromise(
161
166
  Effect.gen(function* () {
162
- const results = yield* Effect.tryPromise(() =>
163
- withTimeout(
164
- firecrawl.search(query, { limit, sources, location, tbs }),
165
- WEB_TOOL_TIMEOUT_MS,
166
- 'Web search',
167
- ),
168
- )
167
+ const results = yield* Effect.tryPromise({
168
+ try: () =>
169
+ withTimeout(
170
+ firecrawl.search(query, { limit, sources, location, tbs }),
171
+ WEB_TOOL_TIMEOUT_MS,
172
+ 'Web search',
173
+ ),
174
+ catch: (cause) => new SearchWebToolError({ message: 'Web search request failed.', cause }),
175
+ })
169
176
  return {
170
177
  query,
171
178
  results: {
@@ -175,7 +182,7 @@ export const searchWebTool = {
175
182
  },
176
183
  citations: buildWebCitations(results),
177
184
  }
178
- }),
185
+ }).pipe(Effect.withSpan('tool.searchWeb.execute')),
179
186
  )
180
187
  },
181
188
  }),
@@ -161,7 +161,7 @@ export function createTeamThinkTool(params: {
161
161
  aiLogger.error`Team-think participant failed (${agentId}): ${error}`
162
162
  },
163
163
  recordAbort: (error: unknown) => {
164
- aiLogger.info`Team-think participant aborted (${agentId}): ${
164
+ aiLogger.debug`Team-think participant aborted (${agentId}): ${
165
165
  error instanceof Error ? error.message : String(error)
166
166
  }`
167
167
  },
@@ -170,7 +170,7 @@ export function createTeamThinkTool(params: {
170
170
  agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
171
171
  observer,
172
172
  }
173
- }),
173
+ }).pipe(Effect.withSpan('tool.teamThink.buildParticipantAgent')),
174
174
  )
175
175
  },
176
176
  }
@@ -1,24 +1,33 @@
1
- import { Schema, Duration, Effect } from 'effect'
1
+ import { Cause, Duration, Effect, Exit, Schema } from 'effect'
2
2
 
3
3
  import { serverLogger } from '../config/logger'
4
4
  import { TimeoutError } from '../effect/errors'
5
- import { getErrorMessage } from './errors'
5
+ import { getErrorMessage, toError } from './errors'
6
6
 
7
7
  class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()('TimedOperationError', {
8
8
  operation: Schema.String,
9
9
  cause: Schema.Defect,
10
10
  }) {}
11
11
 
12
+ function isTimedOperationError(error: unknown): error is TimedOperationError {
13
+ return typeof error === 'object' && error !== null && '_tag' in error && error._tag === 'TimedOperationError'
14
+ }
15
+
12
16
  export function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
13
17
  return Effect.runPromise(
14
18
  Effect.tryPromise({ try: () => promise, catch: (cause) => new TimedOperationError({ operation, cause }) }).pipe(
15
19
  Effect.timeout(Duration.millis(ms)),
16
20
  Effect.catchTag('TimeoutError', () => Effect.fail(new TimeoutError({ operation, ms }))),
17
- Effect.catchTag('TimedOperationError', (error) =>
18
- Effect.fail(error.cause instanceof Error ? error.cause : new Error(String(error.cause))),
19
- ),
21
+ Effect.exit,
20
22
  ),
21
- )
23
+ ).then((exit) => {
24
+ if (Exit.isSuccess(exit)) {
25
+ return exit.value
26
+ }
27
+
28
+ const error = Cause.squash(exit.cause)
29
+ throw isTimedOperationError(error) ? toError(error.cause) : error
30
+ })
22
31
  }
23
32
 
24
33
  export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
@@ -1,5 +1,5 @@
1
1
  export { getErrorMessage } from '@lota-sdk/shared'
2
- import { Match } from 'effect'
2
+ import { Data, Match } from 'effect'
3
3
 
4
4
  import type { EffectError, ValidationIssue } from '../effect/errors'
5
5
  import { BaseServicePersistenceError, isEffectError } from '../effect/errors'
@@ -19,18 +19,16 @@ export interface AppErrorResponse {
19
19
  body: { error: { code: string; message: string } }
20
20
  }
21
21
 
22
- export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => AppErrorResponse }
22
+ export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => unknown }
23
23
 
24
- export class AppError extends Error {
24
+ export class AppError extends Data.Error<{
25
+ readonly message: string
25
26
  readonly code: string
26
27
  readonly statusCode: number
27
-
28
+ }> {
28
29
  constructor(message: string, code: string, statusCode: number) {
29
- super(message)
30
+ super({ message, code, statusCode })
30
31
  this.name = new.target.name
31
- this.code = code
32
- this.statusCode = statusCode
33
- Object.setPrototypeOf(this, new.target.prototype)
34
32
  }
35
33
 
36
34
  toResponse(): AppErrorResponse {
@@ -99,16 +97,30 @@ export function isAppErrorLike(error: unknown): error is AppErrorLike {
99
97
  return typeof candidate.code === 'string' && typeof candidate.statusCode === 'number'
100
98
  }
101
99
 
100
+ function isAppErrorResponse(value: unknown): value is AppErrorResponse {
101
+ if (typeof value !== 'object' || value === null) {
102
+ return false
103
+ }
104
+
105
+ const candidate = value as {
106
+ status?: unknown
107
+ body?: { error?: { code?: unknown; message?: unknown } | null } | null
108
+ }
109
+ return (
110
+ typeof candidate.status === 'number' &&
111
+ typeof candidate.body === 'object' &&
112
+ candidate.body !== null &&
113
+ typeof candidate.body.error === 'object' &&
114
+ candidate.body.error !== null &&
115
+ typeof candidate.body.error.code === 'string' &&
116
+ typeof candidate.body.error.message === 'string'
117
+ )
118
+ }
119
+
102
120
  export function toAppErrorResponse(error: AppErrorLike): AppErrorResponse {
103
121
  if (typeof error.toResponse === 'function') {
104
122
  const response = error.toResponse()
105
- if (
106
- response &&
107
- typeof response === 'object' &&
108
- typeof response.status === 'number' &&
109
- response.body &&
110
- typeof response.body === 'object'
111
- ) {
123
+ if (isAppErrorResponse(response)) {
112
124
  return response
113
125
  }
114
126
  }