@lota-sdk/core 0.4.12 → 0.4.14

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 (139) hide show
  1. package/package.json +4 -4
  2. package/src/ai/embedding-cache.ts +17 -11
  3. package/src/ai-gateway/ai-gateway.ts +164 -94
  4. package/src/ai-gateway/index.ts +4 -1
  5. package/src/config/agent-defaults.ts +2 -2
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/create-runtime.ts +259 -200
  8. package/src/db/cursor-pagination.ts +2 -9
  9. package/src/db/memory-store.ts +194 -175
  10. package/src/db/memory.ts +125 -71
  11. package/src/db/schema-fingerprint.ts +5 -4
  12. package/src/db/service-normalization.ts +4 -3
  13. package/src/db/service.ts +3 -2
  14. package/src/db/startup.ts +15 -16
  15. package/src/effect/errors.ts +161 -21
  16. package/src/effect/index.ts +0 -1
  17. package/src/embeddings/provider.ts +15 -7
  18. package/src/queues/autonomous-job.queue.ts +10 -22
  19. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  20. package/src/queues/document-processor.queue.ts +13 -4
  21. package/src/queues/memory-consolidation.queue.ts +26 -14
  22. package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
  23. package/src/queues/plan-scheduler.queue.ts +37 -15
  24. package/src/queues/queue-factory.ts +59 -35
  25. package/src/queues/standalone-worker.ts +3 -2
  26. package/src/redis/connection.ts +10 -3
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +5 -5
  29. package/src/redis/stream-context.ts +1 -1
  30. package/src/runtime/chat-message.ts +64 -1
  31. package/src/runtime/chat-run-orchestration.ts +33 -20
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  33. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  34. package/src/runtime/domain-layer.ts +13 -7
  35. package/src/runtime/execution-plan.ts +7 -3
  36. package/src/runtime/live-turn-trace.ts +6 -49
  37. package/src/runtime/memory/memory-block.ts +3 -9
  38. package/src/runtime/memory/memory-scope.ts +3 -1
  39. package/src/runtime/plugin-resolution.ts +2 -1
  40. package/src/runtime/post-turn-side-effects.ts +6 -5
  41. package/src/runtime/retrieval-adapters.ts +8 -20
  42. package/src/runtime/runtime-config.ts +3 -9
  43. package/src/runtime/runtime-extensions.ts +2 -4
  44. package/src/runtime/runtime-lifecycle.ts +56 -16
  45. package/src/runtime/runtime-services.ts +180 -102
  46. package/src/runtime/runtime-worker-registry.ts +3 -1
  47. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  48. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  49. package/src/runtime/social-chat/social-chat.ts +356 -223
  50. package/src/runtime/specialist-runner.ts +3 -1
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  52. package/src/runtime/thread-turn-context.ts +142 -102
  53. package/src/runtime/turn-lifecycle.ts +15 -46
  54. package/src/services/agent-activity.service.ts +1 -1
  55. package/src/services/agent-executor.service.ts +107 -77
  56. package/src/services/autonomous-job.service.ts +354 -293
  57. package/src/services/background-work.service.ts +3 -3
  58. package/src/services/context-compaction.service.ts +7 -2
  59. package/src/services/document-chunk.service.ts +50 -32
  60. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  61. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  62. package/src/services/feedback-loop.service.ts +5 -4
  63. package/src/services/graph-full-routing.ts +37 -36
  64. package/src/services/institutional-memory.service.ts +28 -30
  65. package/src/services/learned-skill.service.ts +107 -72
  66. package/src/services/memory/memory-errors.ts +4 -23
  67. package/src/services/memory/memory-org-memory.ts +10 -5
  68. package/src/services/memory/memory-rerank.ts +18 -6
  69. package/src/services/memory/memory.service.ts +170 -111
  70. package/src/services/memory/rerank.service.ts +29 -20
  71. package/src/services/organization-member.service.ts +1 -1
  72. package/src/services/organization.service.ts +69 -75
  73. package/src/services/ownership-dispatcher.service.ts +40 -39
  74. package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
  75. package/src/services/plan/plan-agent-query.service.ts +39 -31
  76. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  77. package/src/services/plan/plan-coordination.service.ts +2 -1
  78. package/src/services/plan/plan-cycle.service.ts +6 -5
  79. package/src/services/plan/plan-deadline.service.ts +57 -54
  80. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  81. package/src/services/plan/plan-executor-graph.ts +18 -15
  82. package/src/services/plan/plan-executor.service.ts +235 -262
  83. package/src/services/plan/plan-run.service.ts +169 -93
  84. package/src/services/plan/plan-scheduler.service.ts +192 -202
  85. package/src/services/plan/plan-template.service.ts +1 -1
  86. package/src/services/plan/plan-transaction-events.ts +1 -1
  87. package/src/services/plan/plan-workspace.service.ts +23 -14
  88. package/src/services/plugin-executor.service.ts +5 -9
  89. package/src/services/queue-job.service.ts +117 -59
  90. package/src/services/recent-activity-title.service.ts +13 -12
  91. package/src/services/recent-activity.service.ts +6 -1
  92. package/src/services/social-chat-history.service.ts +29 -25
  93. package/src/services/system-executor.service.ts +5 -9
  94. package/src/services/thread/thread-active-run.ts +2 -2
  95. package/src/services/thread/thread-listing.ts +61 -57
  96. package/src/services/thread/thread-memory-block.ts +73 -48
  97. package/src/services/thread/thread-message.service.ts +76 -65
  98. package/src/services/thread/thread-record-store.ts +8 -8
  99. package/src/services/thread/thread-title.service.ts +10 -4
  100. package/src/services/thread/thread-turn-execution.ts +43 -45
  101. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  102. package/src/services/thread/thread-turn-streaming.ts +82 -85
  103. package/src/services/thread/thread-turn.ts +8 -8
  104. package/src/services/thread/thread.service.ts +135 -100
  105. package/src/services/user.service.ts +45 -48
  106. package/src/storage/attachment-parser.ts +6 -2
  107. package/src/storage/attachment-storage.service.ts +5 -6
  108. package/src/storage/generated-document-storage.service.ts +1 -1
  109. package/src/system-agents/context-compaction.agent.ts +10 -9
  110. package/src/system-agents/delegated-agent-factory.ts +30 -6
  111. package/src/system-agents/memory-reranker.agent.ts +10 -9
  112. package/src/system-agents/memory.agent.ts +10 -9
  113. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  114. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  115. package/src/system-agents/skill-extractor.agent.ts +13 -12
  116. package/src/system-agents/skill-manager.agent.ts +13 -12
  117. package/src/system-agents/thread-router.agent.ts +10 -5
  118. package/src/system-agents/title-generator.agent.ts +13 -12
  119. package/src/tools/fetch-webpage.tool.ts +13 -13
  120. package/src/tools/memory-block.tool.ts +3 -1
  121. package/src/tools/plan-approval.tool.ts +4 -2
  122. package/src/tools/read-file-parts.tool.ts +10 -4
  123. package/src/tools/remember-memory.tool.ts +3 -1
  124. package/src/tools/research-topic.tool.ts +9 -5
  125. package/src/tools/search-web.tool.ts +16 -16
  126. package/src/tools/search.tool.ts +20 -5
  127. package/src/tools/team-think.tool.ts +61 -38
  128. package/src/utils/async.ts +5 -5
  129. package/src/utils/errors.ts +19 -18
  130. package/src/utils/sse-keepalive.ts +28 -25
  131. package/src/workers/bootstrap.ts +75 -11
  132. package/src/workers/memory-consolidation.worker.ts +82 -91
  133. package/src/workers/organization-learning.worker.ts +14 -4
  134. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  135. package/src/workers/skill-extraction.runner.ts +97 -61
  136. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  137. package/src/workers/utils/thread-message-query.ts +24 -24
  138. package/src/workers/worker-utils.ts +23 -4
  139. package/src/effect/helpers.ts +0 -123
@@ -2,12 +2,7 @@ import type { OwnershipDispatchContext, PlanNodeSpec, PluginPlanNodeOwner } from
2
2
  import { Context, Effect, Layer } from 'effect'
3
3
 
4
4
  import { BadRequestError, ServiceError } from '../effect/errors'
5
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
5
  import { RuntimeConfigServiceTag } from '../effect/services'
7
-
8
- const tryPluginExecutorPromise = makeEffectTryPromiseWithMessage(
9
- (message, cause) => new ServiceError({ message, cause }),
10
- )
11
6
  import type { LotaPlugin, PluginNodeExecutionParams } from '../runtime/plugin-types'
12
7
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
13
8
  import type { PlanValidationIssueInput } from './plan/plan-validator.service'
@@ -107,8 +102,8 @@ export function makePluginExecutorService(config: ResolvedLotaRuntimeConfig) {
107
102
  })
108
103
  }
109
104
 
110
- return yield* tryPluginExecutorPromise(
111
- () =>
105
+ return yield* Effect.tryPromise({
106
+ try: () =>
112
107
  nodeExecutor.executeNode(
113
108
  buildPluginExecutionParams({
114
109
  owner,
@@ -117,8 +112,9 @@ export function makePluginExecutorService(config: ResolvedLotaRuntimeConfig) {
117
112
  context: params.context,
118
113
  }),
119
114
  ),
120
- `Plugin executor "${owner.ref}.${owner.operation}" failed.`,
121
- ).pipe(Effect.withSpan('PluginExecutor.invoke'))
115
+ catch: (cause) =>
116
+ new ServiceError({ message: `Plugin executor "${owner.ref}.${owner.operation}" failed.`, cause }),
117
+ }).pipe(Effect.withSpan('PluginExecutor.invoke'))
122
118
  }),
123
119
  }
124
120
  }
@@ -14,7 +14,6 @@ import type { SurrealDBService } from '../db/service'
14
14
  import { TABLES } from '../db/tables'
15
15
  import { isRetriableTransactionConflict } from '../db/transaction-conflict'
16
16
  import { BadRequestError, DatabaseError } from '../effect/errors'
17
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
18
17
  import { DatabaseServiceTag } from '../effect/services'
19
18
  import { createDeterministicRecordId } from '../utils/crypto'
20
19
  import { nowDate, unsafeDateFrom } from '../utils/date-time'
@@ -185,17 +184,6 @@ function getQueuedStatus(job: TrackedBullJobLike): QueueJobStatus {
185
184
  return typeof delay === 'number' && delay > 0 ? 'delayed' : 'waiting'
186
185
  }
187
186
 
188
- const effectTryQueueJobPersistence = makeEffectTryPromiseWithMessage(
189
- (message, cause) => new DatabaseError({ message, cause }),
190
- )
191
-
192
- function tryQueueJobPersistence<A>(
193
- message: string,
194
- evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
195
- ): Effect.Effect<A, DatabaseError> {
196
- return effectTryQueueJobPersistence(evaluate, message)
197
- }
198
-
199
187
  function withPersistenceRetry<T>(work: Effect.Effect<T, DatabaseError>): Effect.Effect<T, DatabaseError> {
200
188
  return Effect.retry(work, {
201
189
  times: PERSISTENCE_MAX_ATTEMPTS - 1,
@@ -219,7 +207,14 @@ export function makeQueueJobService(db: SurrealDBService) {
219
207
 
220
208
  recordEnqueued(job: TrackedBullJobLike, context?: Record<string, unknown>) {
221
209
  return Effect.gen(function* () {
222
- yield* tryQueueJobPersistence('Failed to connect before recording queued job metadata', () => db.connect())
210
+ yield* db
211
+ .connect()
212
+ .pipe(
213
+ Effect.mapError(
214
+ (cause) =>
215
+ new DatabaseError({ message: 'Failed to connect before recording queued job metadata', cause }),
216
+ ),
217
+ )
223
218
 
224
219
  const bullmqJobId = yield* getBullmqJobId(job)
225
220
  const queueJobId = yield* getQueueJobRecordId(job)
@@ -228,8 +223,8 @@ export function makeQueueJobService(db: SurrealDBService) {
228
223
 
229
224
  return yield* withPersistenceRetry(
230
225
  Effect.gen(function* () {
231
- yield* tryQueueJobPersistence('Failed to upsert queued job metadata', () =>
232
- db.upsert(
226
+ yield* db
227
+ .upsert(
233
228
  TABLES.QUEUE_JOB,
234
229
  queueJobId,
235
230
  compactRecord({
@@ -245,8 +240,12 @@ export function makeQueueJobService(db: SurrealDBService) {
245
240
  queuedAt,
246
241
  }),
247
242
  QueueJobRowSchema,
248
- ),
249
- )
243
+ )
244
+ .pipe(
245
+ Effect.mapError(
246
+ (cause) => new DatabaseError({ message: 'Failed to upsert queued job metadata', cause }),
247
+ ),
248
+ )
250
249
 
251
250
  return recordIdToString(queueJobId, TABLES.QUEUE_JOB)
252
251
  }),
@@ -256,7 +255,14 @@ export function makeQueueJobService(db: SurrealDBService) {
256
255
 
257
256
  markAttemptStarted(job: TrackedBullJobLike) {
258
257
  return Effect.gen(function* () {
259
- yield* tryQueueJobPersistence('Failed to connect before marking queue job attempt started', () => db.connect())
258
+ yield* db
259
+ .connect()
260
+ .pipe(
261
+ Effect.mapError(
262
+ (cause) =>
263
+ new DatabaseError({ message: 'Failed to connect before marking queue job attempt started', cause }),
264
+ ),
265
+ )
260
266
 
261
267
  const attemptNumber = resolveAttemptNumber(job)
262
268
  const bullmqJobId = yield* getBullmqJobId(job).pipe(
@@ -273,8 +279,8 @@ export function makeQueueJobService(db: SurrealDBService) {
273
279
 
274
280
  return yield* withPersistenceRetry(
275
281
  Effect.gen(function* () {
276
- yield* tryQueueJobPersistence('Failed to upsert queue job start metadata', () =>
277
- db.upsert(
282
+ yield* db
283
+ .upsert(
278
284
  TABLES.QUEUE_JOB,
279
285
  queueJobId,
280
286
  compactRecord({
@@ -292,17 +298,25 @@ export function makeQueueJobService(db: SurrealDBService) {
292
298
  startedAt,
293
299
  }),
294
300
  QueueJobRowSchema,
295
- ),
296
- )
297
-
298
- yield* tryQueueJobPersistence('Failed to upsert queue job attempt start metadata', () =>
299
- db.upsert(
301
+ )
302
+ .pipe(
303
+ Effect.mapError(
304
+ (cause) => new DatabaseError({ message: 'Failed to upsert queue job start metadata', cause }),
305
+ ),
306
+ )
307
+
308
+ yield* db
309
+ .upsert(
300
310
  TABLES.QUEUE_JOB_ATTEMPT,
301
311
  attemptId,
302
312
  { queueJobId, attemptNumber, status: 'active', startedAt },
303
313
  QueueJobAttemptRowSchema,
304
- ),
305
- )
314
+ )
315
+ .pipe(
316
+ Effect.mapError(
317
+ (cause) => new DatabaseError({ message: 'Failed to upsert queue job attempt start metadata', cause }),
318
+ ),
319
+ )
306
320
 
307
321
  return recordIdToString(queueJobId, TABLES.QUEUE_JOB)
308
322
  }),
@@ -312,9 +326,14 @@ export function makeQueueJobService(db: SurrealDBService) {
312
326
 
313
327
  markAttemptCompleted(job: TrackedBullJobLike, result: unknown) {
314
328
  return Effect.gen(function* () {
315
- yield* tryQueueJobPersistence('Failed to connect before marking queue job attempt completed', () =>
316
- db.connect(),
317
- )
329
+ yield* db
330
+ .connect()
331
+ .pipe(
332
+ Effect.mapError(
333
+ (cause) =>
334
+ new DatabaseError({ message: 'Failed to connect before marking queue job attempt completed', cause }),
335
+ ),
336
+ )
318
337
 
319
338
  const attemptNumber = resolveAttemptNumber(job)
320
339
  const bullmqJobId = yield* getBullmqJobId(job)
@@ -325,13 +344,20 @@ export function makeQueueJobService(db: SurrealDBService) {
325
344
 
326
345
  yield* withPersistenceRetry(
327
346
  Effect.gen(function* () {
328
- const existingAttempt = yield* tryQueueJobPersistence(
329
- 'Failed to load existing queue job attempt before completion update',
330
- () => db.findOne(TABLES.QUEUE_JOB_ATTEMPT, { id: attemptId }, QueueJobAttemptRowSchema),
331
- )
332
-
333
- yield* tryQueueJobPersistence('Failed to upsert completed queue job attempt metadata', () =>
334
- db.upsert(
347
+ const existingAttempt = yield* db
348
+ .findOne(TABLES.QUEUE_JOB_ATTEMPT, { id: attemptId }, QueueJobAttemptRowSchema)
349
+ .pipe(
350
+ Effect.mapError(
351
+ (cause) =>
352
+ new DatabaseError({
353
+ message: 'Failed to load existing queue job attempt before completion update',
354
+ cause,
355
+ }),
356
+ ),
357
+ )
358
+
359
+ yield* db
360
+ .upsert(
335
361
  TABLES.QUEUE_JOB_ATTEMPT,
336
362
  attemptId,
337
363
  compactRecord({
@@ -346,11 +372,16 @@ export function makeQueueJobService(db: SurrealDBService) {
346
372
  : 0,
347
373
  }),
348
374
  QueueJobAttemptRowSchema,
349
- ),
350
- )
351
-
352
- yield* tryQueueJobPersistence('Failed to upsert completed queue job metadata', () =>
353
- db.upsert(
375
+ )
376
+ .pipe(
377
+ Effect.mapError(
378
+ (cause) =>
379
+ new DatabaseError({ message: 'Failed to upsert completed queue job attempt metadata', cause }),
380
+ ),
381
+ )
382
+
383
+ yield* db
384
+ .upsert(
354
385
  TABLES.QUEUE_JOB,
355
386
  queueJobId,
356
387
  compactRecord({
@@ -370,8 +401,12 @@ export function makeQueueJobService(db: SurrealDBService) {
370
401
  lastError: undefined,
371
402
  }),
372
403
  QueueJobRowSchema,
373
- ),
374
- )
404
+ )
405
+ .pipe(
406
+ Effect.mapError(
407
+ (cause) => new DatabaseError({ message: 'Failed to upsert completed queue job metadata', cause }),
408
+ ),
409
+ )
375
410
  }),
376
411
  )
377
412
  })
@@ -379,7 +414,14 @@ export function makeQueueJobService(db: SurrealDBService) {
379
414
 
380
415
  markAttemptFailed(job: TrackedBullJobLike, error: unknown) {
381
416
  return Effect.gen(function* () {
382
- yield* tryQueueJobPersistence('Failed to connect before marking queue job attempt failed', () => db.connect())
417
+ yield* db
418
+ .connect()
419
+ .pipe(
420
+ Effect.mapError(
421
+ (cause) =>
422
+ new DatabaseError({ message: 'Failed to connect before marking queue job attempt failed', cause }),
423
+ ),
424
+ )
383
425
 
384
426
  const attemptNumber = resolveAttemptNumber(job)
385
427
  const bullmqJobId = yield* getBullmqJobId(job)
@@ -393,13 +435,20 @@ export function makeQueueJobService(db: SurrealDBService) {
393
435
 
394
436
  yield* withPersistenceRetry(
395
437
  Effect.gen(function* () {
396
- const existingAttempt = yield* tryQueueJobPersistence(
397
- 'Failed to load existing queue job attempt before failure update',
398
- () => db.findOne(TABLES.QUEUE_JOB_ATTEMPT, { id: attemptId }, QueueJobAttemptRowSchema),
399
- )
400
-
401
- yield* tryQueueJobPersistence('Failed to upsert failed queue job attempt metadata', () =>
402
- db.upsert(
438
+ const existingAttempt = yield* db
439
+ .findOne(TABLES.QUEUE_JOB_ATTEMPT, { id: attemptId }, QueueJobAttemptRowSchema)
440
+ .pipe(
441
+ Effect.mapError(
442
+ (cause) =>
443
+ new DatabaseError({
444
+ message: 'Failed to load existing queue job attempt before failure update',
445
+ cause,
446
+ }),
447
+ ),
448
+ )
449
+
450
+ yield* db
451
+ .upsert(
403
452
  TABLES.QUEUE_JOB_ATTEMPT,
404
453
  attemptId,
405
454
  compactRecord({
@@ -414,11 +463,16 @@ export function makeQueueJobService(db: SurrealDBService) {
414
463
  : 0,
415
464
  }),
416
465
  QueueJobAttemptRowSchema,
417
- ),
418
- )
419
-
420
- yield* tryQueueJobPersistence('Failed to upsert failed queue job metadata', () =>
421
- db.upsert(
466
+ )
467
+ .pipe(
468
+ Effect.mapError(
469
+ (cause) =>
470
+ new DatabaseError({ message: 'Failed to upsert failed queue job attempt metadata', cause }),
471
+ ),
472
+ )
473
+
474
+ yield* db
475
+ .upsert(
422
476
  TABLES.QUEUE_JOB,
423
477
  queueJobId,
424
478
  compactRecord({
@@ -437,8 +491,12 @@ export function makeQueueJobService(db: SurrealDBService) {
437
491
  lastError: normalizedError,
438
492
  }),
439
493
  QueueJobRowSchema,
440
- ),
441
- )
494
+ )
495
+ .pipe(
496
+ Effect.mapError(
497
+ (cause) => new DatabaseError({ message: 'Failed to upsert failed queue job metadata', cause }),
498
+ ),
499
+ )
442
500
  }),
443
501
  )
444
502
  })
@@ -1,14 +1,15 @@
1
1
  import { Context, Effect, Layer } from 'effect'
2
2
 
3
+ import { AiGatewayModelsTag } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
3
5
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
4
6
  import { ServiceError } from '../effect/errors'
5
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
7
  import { AgentConfigServiceTag } from '../effect/services'
7
8
  import type { HelperModelRuntime } from '../runtime/helper-model'
8
9
  import { HelperModelTag } from '../runtime/helper-model'
9
10
  import { normalizeTitle } from '../runtime/title-helpers'
10
11
  import {
11
- createRecentActivityTitleRefinerAgent,
12
+ makeRecentActivityTitleRefinerAgentFactory,
12
13
  RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
13
14
  } from '../system-agents/recent-activity-title-refiner.agent'
14
15
  import type { makeRecentActivityService } from './recent-activity.service'
@@ -16,10 +17,6 @@ import { RecentActivityServiceTag } from './recent-activity.service'
16
17
 
17
18
  const RECENT_ACTIVITY_TITLE_TIMEOUT_MS = 60_000
18
19
 
19
- const tryRecentActivityTitlePromise = makeEffectTryPromiseWithMessage(
20
- (message, cause) => new ServiceError({ message, cause }),
21
- )
22
-
23
20
  function buildRefinementPromptInput(
24
21
  candidate: {
25
22
  id: string
@@ -48,9 +45,11 @@ function buildRefinementPromptInput(
48
45
 
49
46
  export function makeRecentActivityTitleService(
50
47
  agentConfig: ResolvedAgentConfig,
48
+ aiGatewayModels: AiGatewayModels,
51
49
  recentActivityService: ReturnType<typeof makeRecentActivityService>,
52
50
  helperModelRuntime: HelperModelRuntime,
53
51
  ) {
52
+ const refinerAgentFactory = makeRecentActivityTitleRefinerAgentFactory(aiGatewayModels, agentConfig)
54
53
  const refineRecentActivityTitle = (activityId: string) =>
55
54
  Effect.gen(function* () {
56
55
  const candidate = yield* recentActivityService.getRefinementCandidate(activityId)
@@ -64,17 +63,18 @@ export function makeRecentActivityTitleService(
64
63
  }
65
64
 
66
65
  const refinedTitle = normalizeTitle(
67
- yield* tryRecentActivityTitlePromise(
68
- () =>
66
+ yield* Effect.tryPromise({
67
+ try: () =>
69
68
  helperModelRuntime.generateHelperText({
70
69
  tag: 'recent-activity-title-refinement',
71
- createAgent: (options) => createRecentActivityTitleRefinerAgent(agentConfig, options),
70
+ createAgent: refinerAgentFactory,
72
71
  defaultSystemPrompt: RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
73
72
  timeoutMs: RECENT_ACTIVITY_TITLE_TIMEOUT_MS,
74
73
  messages: [{ role: 'user', content: promptInput }],
75
74
  }),
76
- 'Failed to generate recent activity title refinement.',
77
- ),
75
+ catch: (cause) =>
76
+ new ServiceError({ message: 'Failed to generate recent activity title refinement.', cause }),
77
+ }),
78
78
  )
79
79
  if (
80
80
  !recentActivityService.isAgentTitleUseful({
@@ -101,8 +101,9 @@ export const RecentActivityTitleServiceLive = Layer.effect(
101
101
  RecentActivityTitleServiceTag,
102
102
  Effect.gen(function* () {
103
103
  const agentConfig = yield* AgentConfigServiceTag
104
+ const aiGatewayModels = yield* AiGatewayModelsTag
104
105
  const recentActivityService = yield* RecentActivityServiceTag
105
106
  const helperModelRuntime = yield* HelperModelTag
106
- return makeRecentActivityTitleService(agentConfig, recentActivityService, helperModelRuntime)
107
+ return makeRecentActivityTitleService(agentConfig, aiGatewayModels, recentActivityService, helperModelRuntime)
107
108
  }),
108
109
  )
@@ -22,6 +22,7 @@ import type { RecordIdInput, RecordIdRef } from '../db/record-id'
22
22
  import type { SurrealDBService } from '../db/service'
23
23
  import { TABLES } from '../db/tables'
24
24
  import { DatabaseServiceTag } from '../effect/services'
25
+ import { toValidationError } from '../effect/zod'
25
26
  import { createDeterministicRecordId } from '../utils/crypto'
26
27
  import { nowDate, toIsoDateTimeString, toOptionalIsoDateTimeString, unsafeDateFrom } from '../utils/date-time'
27
28
  import { compactRecord, compactWhitespace, truncateText } from '../utils/string'
@@ -156,7 +157,11 @@ export function makeRecentActivityService(db: SurrealDBService) {
156
157
  event: RecentActivityEventInput
157
158
  }) {
158
159
  return Effect.gen(function* () {
159
- const parsedEvent = sanitizeEvent(RecentActivityEventInputSchema.parse(params.event))
160
+ const parsedInput = yield* Effect.try({
161
+ try: () => RecentActivityEventInputSchema.parse(params.event),
162
+ catch: (cause) => toValidationError(cause, 'Failed to validate recent activity event.'),
163
+ })
164
+ const parsedEvent = sanitizeEvent(parsedInput)
160
165
  const orgIdString = recordIdToString(params.orgId, TABLES.ORGANIZATION)
161
166
  const userIdString = recordIdToString(params.userId, TABLES.USER)
162
167
  const eventRecordId = createDeterministicRecordId(
@@ -2,21 +2,16 @@ import { normalizeMessageBatch } from '@lota-sdk/shared'
2
2
  import { Context, Effect, Layer, Schema } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
5
  import { RedisServiceTag, RuntimeConfigServiceTag } from '../effect/services'
7
6
  import type { RedisConnectionManager } from '../redis/connection'
8
7
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
9
8
  import type { LotaRuntimeBackgroundCursor, LotaRuntimeBackgroundCursorKind } from '../runtime/runtime-extensions'
10
9
 
11
10
  export class SocialChatHistoryError extends Schema.TaggedErrorClass<SocialChatHistoryError>()(
12
- 'SocialChatHistoryError',
11
+ '@lota-sdk/core/SocialChatHistoryError',
13
12
  { message: Schema.String, cause: Schema.optional(Schema.Defect) },
14
13
  ) {}
15
14
 
16
- const tryRedisPromise = makeEffectTryPromiseWithMessage(
17
- (message, cause) => new SocialChatHistoryError({ message, cause }),
18
- )
19
-
20
15
  const DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX = 'lota:social:history'
21
16
 
22
17
  const SocialChatMessageRoleSchema = z.enum(['user', 'assistant'])
@@ -157,20 +152,26 @@ export function makeSocialChatHistoryService(
157
152
  multi.zadd(workspaceIndexKey(message.workspaceId), score, storageKey)
158
153
  }
159
154
 
160
- yield* tryRedisPromise(() => multi.exec(), 'Failed to upsert social chat messages.')
155
+ yield* Effect.tryPromise({
156
+ try: () => multi.exec(),
157
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to upsert social chat messages.', cause }),
158
+ })
161
159
  return normalizedMessages
162
160
  })
163
161
 
164
162
  const listThreadMessagesEffect = (params: { workspaceId: string; threadId: string }) =>
165
163
  Effect.gen(function* () {
166
164
  const conn = redis.getConnection()
167
- const storageKeys = yield* tryRedisPromise(
168
- () => conn.zrange(threadIndexKey(params.workspaceId, params.threadId), 0, -1),
169
- 'Failed to list thread message keys.',
170
- )
165
+ const storageKeys = yield* Effect.tryPromise({
166
+ try: () => conn.zrange(threadIndexKey(params.workspaceId, params.threadId), 0, -1),
167
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to list thread message keys.', cause }),
168
+ })
171
169
  if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
172
170
 
173
- const storedValues = yield* tryRedisPromise(() => conn.mget(storageKeys), 'Failed to read thread messages.')
171
+ const storedValues = yield* Effect.tryPromise({
172
+ try: () => conn.mget(storageKeys),
173
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to read thread messages.', cause }),
174
+ })
174
175
  const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
175
176
  return parsedMessages
176
177
  .filter((message): message is SocialChatHistoryMessage => message !== null)
@@ -188,16 +189,19 @@ export function makeSocialChatHistoryService(
188
189
  const scoreStart =
189
190
  params.cursor?.createdAt.getTime() ??
190
191
  (params.onboardingCutoff ? params.onboardingCutoff.getTime() : Number.NEGATIVE_INFINITY)
191
- const storageKeys = yield* tryRedisPromise(
192
- () =>
192
+ const storageKeys = yield* Effect.tryPromise({
193
+ try: () =>
193
194
  params.cursor || params.onboardingCutoff
194
195
  ? conn.zrangebyscore(indexKey, scoreStart, '+inf')
195
196
  : conn.zrange(indexKey, 0, -1),
196
- 'Failed to list workspace message keys.',
197
- )
197
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to list workspace message keys.', cause }),
198
+ })
198
199
  if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
199
200
 
200
- const storedValues = yield* tryRedisPromise(() => conn.mget(storageKeys), 'Failed to read workspace messages.')
201
+ const storedValues = yield* Effect.tryPromise({
202
+ try: () => conn.mget(storageKeys),
203
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to read workspace messages.', cause }),
204
+ })
201
205
  const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
202
206
  return parsedMessages
203
207
  .filter((message): message is SocialChatHistoryMessage => message !== null)
@@ -225,20 +229,20 @@ export function makeSocialChatHistoryService(
225
229
  }).pipe(Effect.map((messages) => messages.length > 0))
226
230
 
227
231
  const getBackgroundCursorEffect = (kind: LotaRuntimeBackgroundCursorKind, workspaceId: string) =>
228
- tryRedisPromise(
229
- () => redis.getConnection().get(cursorKey(kind, workspaceId)),
230
- 'Failed to read background cursor.',
231
- ).pipe(Effect.flatMap((value) => parseCursorEffect(value)))
232
+ Effect.tryPromise({
233
+ try: () => redis.getConnection().get(cursorKey(kind, workspaceId)),
234
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to read background cursor.', cause }),
235
+ }).pipe(Effect.flatMap((value) => parseCursorEffect(value)))
232
236
 
233
237
  const setBackgroundCursorEffect = (
234
238
  kind: LotaRuntimeBackgroundCursorKind,
235
239
  workspaceId: string,
236
240
  cursor: LotaRuntimeBackgroundCursor,
237
241
  ) =>
238
- tryRedisPromise(
239
- () => redis.getConnection().set(cursorKey(kind, workspaceId), serializeCursor(cursor)),
240
- 'Failed to write background cursor.',
241
- ).pipe(Effect.asVoid)
242
+ Effect.tryPromise({
243
+ try: () => redis.getConnection().set(cursorKey(kind, workspaceId), serializeCursor(cursor)),
244
+ catch: (cause) => new SocialChatHistoryError({ message: 'Failed to write background cursor.', cause }),
245
+ }).pipe(Effect.asVoid)
242
246
 
243
247
  return {
244
248
  upsertMessages: upsertMessagesEffect,
@@ -2,12 +2,7 @@ import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlan
2
2
  import { Context, Effect, Layer } from 'effect'
3
3
 
4
4
  import { BadRequestError, ServiceError } from '../effect/errors'
5
- import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
6
5
  import { RuntimeConfigServiceTag } from '../effect/services'
7
-
8
- const trySystemExecutorPromise = makeEffectTryPromiseWithMessage(
9
- (message, cause) => new ServiceError({ message, cause }),
10
- )
11
6
  import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
12
7
  import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
13
8
  import type { PlanValidationIssueInput } from './plan/plan-validator.service'
@@ -110,8 +105,8 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
110
105
  })
111
106
  }
112
107
 
113
- return yield* trySystemExecutorPromise(
114
- () =>
108
+ return yield* Effect.tryPromise({
109
+ try: () =>
115
110
  executor.executeNode(
116
111
  buildSystemExecutionParams({
117
112
  owner,
@@ -120,8 +115,9 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
120
115
  context: params.context,
121
116
  }),
122
117
  ),
123
- `System executor "${owner.ref}.${owner.operation}" failed.`,
124
- ).pipe(Effect.withSpan('SystemExecutor.invoke'))
118
+ catch: (cause) =>
119
+ new ServiceError({ message: `System executor "${owner.ref}.${owner.operation}" failed.`, cause }),
120
+ }).pipe(Effect.withSpan('SystemExecutor.invoke'))
125
121
  }),
126
122
  }
127
123
  }
@@ -6,7 +6,7 @@ import type { RecordIdRef } from '../../db/record-id'
6
6
  import { ensureRecordId, recordIdToString } from '../../db/record-id'
7
7
  import type { SurrealDBService } from '../../db/service'
8
8
  import { TABLES } from '../../db/tables'
9
- import { ActiveThreadRunConflictError, DatabaseError, RedisError } from '../../effect/errors'
9
+ import { ERROR_TAGS, ActiveThreadRunConflictError, DatabaseError, RedisError } from '../../effect/errors'
10
10
  import type { RedisConnectionManager } from '../../redis/connection'
11
11
  import { withLeaseLock } from '../../redis/redis-lease-lock'
12
12
  import type { ThreadRecordStore } from './thread-record-store'
@@ -176,7 +176,7 @@ export function createThreadActiveRunHelpers(deps: {
176
176
  },
177
177
  fn,
178
178
  ).pipe(
179
- Effect.catchTag('LockAcquisitionError', () =>
179
+ Effect.catchTag(ERROR_TAGS.LockAcquisitionError, () =>
180
180
  Effect.gen(function* () {
181
181
  const activeRunId = (yield* Effect.catch(getActiveRunIdEffect(threadId), () => Effect.succeed(null))) ?? ''
182
182
  return yield* new ActiveThreadRunConflictError({