@lota-sdk/core 0.4.36 → 0.4.37

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.36",
3
+ "version": "0.4.37",
4
4
  "files": [
5
5
  "src",
6
6
  "infrastructure/schema"
@@ -32,7 +32,7 @@
32
32
  "@ai-sdk/provider": "^3.0.9",
33
33
  "@chat-adapter/slack": "^4.26.0",
34
34
  "@chat-adapter/state-ioredis": "^4.26.0",
35
- "@lota-sdk/shared": "0.4.36",
35
+ "@lota-sdk/shared": "0.4.37",
36
36
  "@mendable/firecrawl-js": "^4.20.0",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.170",
@@ -1,3 +1,4 @@
1
+ import type { PlanFailureClass } from '@lota-sdk/shared'
1
2
  import { Context, Schema, Effect, Layer } from 'effect'
2
3
 
3
4
  import type { ResolvedAgentConfig } from '../../config/agent-defaults'
@@ -39,6 +40,18 @@ function buildWakeDedupeKey(params: {
39
40
  return `${params.organizationId}:${params.threadId}:${params.runId}:${params.nodeId}:${params.agentId}`
40
41
  }
41
42
 
43
+ function classifyPlanTurnDispatchFailure(error: unknown): PlanFailureClass {
44
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
45
+ if (message.includes('timeout')) {
46
+ return 'timeout_exceeded'
47
+ }
48
+ return 'non_recoverable_logic_error'
49
+ }
50
+
51
+ function formatPlanTurnDispatchError(error: unknown): string {
52
+ return error instanceof Error ? error.message : String(error)
53
+ }
54
+
42
55
  class PlanAgentHeartbeatError extends Schema.TaggedErrorClass<PlanAgentHeartbeatError>()(
43
56
  ERROR_TAGS.PlanAgentHeartbeatError,
44
57
  { operation: Schema.String, cause: Schema.Defect },
@@ -151,9 +164,27 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
151
164
  )
152
165
  }
153
166
 
154
- yield* heartbeatServiceEffect(
155
- 'trigger-plan-node-turn',
156
- threadTurnService.triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId }),
167
+ yield* threadTurnService.triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId }).pipe(
168
+ Effect.catch((cause) =>
169
+ heartbeatServiceEffect(
170
+ 'block-node-on-plan-turn-failure',
171
+ planExecutorService.blockNodeOnDispatchFailure({
172
+ threadId: threadRef,
173
+ runId: params.runId,
174
+ nodeId: params.nodeId,
175
+ emittedBy: 'plan-agent-heartbeat',
176
+ message: formatPlanTurnDispatchError(cause),
177
+ failureClass: classifyPlanTurnDispatchFailure(cause),
178
+ }),
179
+ ).pipe(
180
+ Effect.tap(() =>
181
+ Effect.sync(() => {
182
+ serverLogger.warn`Plan agent heartbeat blocked ${params.runId}/${params.nodeId} after plan-turn failure: ${formatPlanTurnDispatchError(cause)}`
183
+ }),
184
+ ),
185
+ Effect.asVoid,
186
+ ),
187
+ ),
157
188
  )
158
189
  return true
159
190
  }),
@@ -8,14 +8,13 @@ import {
8
8
  makeAiGatewayService,
9
9
  } from '../ai-gateway/ai-gateway'
10
10
  import type { AiGatewayModels, RuntimeBridge } from '../ai-gateway/ai-gateway'
11
- import { EmbeddingCacheLive } from '../ai/embedding-cache'
12
11
  import { serverLogger } from '../config/logger'
13
12
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
14
13
  import { buildWorkerInfrastructureLayer } from '../effect'
15
14
  import { ERROR_TAGS } from '../effect/errors'
16
15
  import { DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
16
+ import { buildDomainServiceLayer } from '../runtime/domain-layer'
17
17
  import { lotaRuntimeEnvConfig, parseLotaRuntimeConfig, parseWorkerBootstrapEnv } from '../runtime/runtime-config'
18
- import { FirecrawlLive } from '../tools/firecrawl-client'
19
18
  import { getErrorMessage } from '../utils/errors'
20
19
 
21
20
  class SandboxedWorkerBootstrapError extends Schema.TaggedErrorClass<SandboxedWorkerBootstrapError>()(
@@ -103,11 +102,7 @@ function ensureSandboxedWorkerRuntimeConfigured(): Promise<WorkerManagedRuntime>
103
102
  Layer.effect(AiGatewayModelsTag, Deferred.await(aiGatewayModelsDeferred)),
104
103
  Layer.effect(RuntimeBridgeTag, Deferred.await(runtimeBridgeDeferred)),
105
104
  )
106
- const layerWithBridge = Layer.mergeAll(infrastructureLayer, bridgeLayer)
107
- const fullLayer = Layer.mergeAll(
108
- layerWithBridge,
109
- Layer.provide(Layer.mergeAll(EmbeddingCacheLive, FirecrawlLive), layerWithBridge),
110
- )
105
+ const fullLayer = buildDomainServiceLayer(infrastructureLayer, bridgeLayer)
111
106
 
112
107
  const managedRuntime = ManagedRuntime.make(fullLayer)
113
108
  const runtimeBridge: RuntimeBridge = {
@@ -7,11 +7,12 @@ import { serverLogger } from '../config/logger'
7
7
  import {
8
8
  AgentConfigServiceTag,
9
9
  DatabaseServiceTag,
10
+ RedisServiceTag,
10
11
  RuntimeAdaptersServiceTag,
11
12
  RuntimeConfigServiceTag,
12
13
  } from '../effect/services'
14
+ import { makeOrganizationLearningQueueRuntime } from '../queues/organization-learning.queue'
13
15
  import type { OrganizationLearningJob } from '../queues/organization-learning.queue'
14
- import { LotaQueuesServiceTag } from '../queues/queues.service'
15
16
  import { LearnedSkillServiceTag } from '../services/learned-skill.service'
16
17
  import { MemoryServiceTag } from '../services/memory/memory.service'
17
18
  import { QueueJobServiceTag } from '../services/queue-job.service'
@@ -28,7 +29,12 @@ const runtime = await initializeSandboxedWorkerRuntime()
28
29
  const resolve = <I, T>(tag: Context.Key<I, T>): Promise<T> => runtime.runPromise(Effect.service(tag))
29
30
  const agentConfig = await resolve(AgentConfigServiceTag)
30
31
  const aiGatewayModels = await resolve(AiGatewayModelsTag)
31
- const queues = await resolve(LotaQueuesServiceTag)
32
+ const redis = await resolve(RedisServiceTag)
33
+ const organizationLearningQueueJobService = await resolve(QueueJobServiceTag)
34
+ const organizationLearningQueue = makeOrganizationLearningQueueRuntime({
35
+ connectionProvider: () => redis.getConnectionForBullMQ(),
36
+ queueJobService: organizationLearningQueueJobService,
37
+ })
32
38
  const regularChatDigestServices: RegularChatDigestServices = {
33
39
  agentConfig,
34
40
  aiGatewayModels,
@@ -36,7 +42,7 @@ const regularChatDigestServices: RegularChatDigestServices = {
36
42
  memoryService: await resolve(MemoryServiceTag),
37
43
  socialChatHistoryService: await resolve(SocialChatHistoryServiceTag),
38
44
  runtimeAdapters: await resolve(RuntimeAdaptersServiceTag),
39
- organizationLearningQueue: queues.organizationLearning,
45
+ organizationLearningQueue,
40
46
  }
41
47
  const workerRuntimeConfig = await resolve(RuntimeConfigServiceTag)
42
48
  const skillExtractionServices: SkillExtractionServices = {
@@ -49,7 +55,6 @@ const skillExtractionServices: SkillExtractionServices = {
49
55
  embeddingModel: workerRuntimeConfig.aiGateway.embeddingModel,
50
56
  runPromise: (effect) => runtime.runPromise(effect),
51
57
  }
52
- const organizationLearningQueueJobService = await resolve(QueueJobServiceTag)
53
58
 
54
59
  // One sandboxed worker handles both organization-learning branches so the
55
60
  // queue can dispatch digest and skill jobs without separate worker images.