@lota-sdk/core 0.4.9 → 0.4.11
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 +2 -2
- package/src/ai/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +164 -82
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -107
- package/src/config/agent-types.ts +1 -1
- package/src/config/background-processing.ts +1 -1
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +22 -25
- package/src/config/thread-defaults.ts +1 -10
- package/src/create-runtime.ts +145 -670
- package/src/db/base.service.ts +30 -38
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.ts +29 -20
- package/src/db/memory.ts +188 -195
- package/src/db/service-normalization.ts +97 -64
- package/src/db/service.ts +496 -384
- package/src/db/startup.ts +30 -19
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -7
- package/src/effect/layers.ts +75 -72
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -71
- package/src/index.ts +13 -12
- package/src/queues/autonomous-job.queue.ts +177 -143
- package/src/queues/context-compaction.queue.ts +41 -39
- package/src/queues/delayed-node-promotion.queue.ts +61 -42
- package/src/queues/document-processor.queue.ts +5 -3
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +79 -53
- package/src/queues/organization-learning.queue.ts +70 -33
- package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
- package/src/queues/plan-scheduler.queue.ts +101 -97
- package/src/queues/post-chat-memory.queue.ts +56 -46
- package/src/queues/queue-factory.ts +146 -69
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +44 -44
- package/src/redis/connection.ts +181 -164
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +10 -5
- package/src/runtime/agent-stream-helpers.ts +24 -15
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +131 -85
- package/src/runtime/domain-layer.ts +203 -0
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -14
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/memory/memory-block.ts +19 -9
- package/src/runtime/memory/memory-pipeline.ts +53 -66
- package/src/runtime/memory/memory-scope.ts +33 -29
- package/src/runtime/plugin-resolution.ts +58 -62
- package/src/runtime/post-turn-side-effects.ts +139 -161
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +0 -43
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +455 -0
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
- package/src/runtime/social-chat/social-chat-history.ts +24 -13
- package/src/runtime/social-chat/social-chat.ts +420 -369
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
- package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
- package/src/runtime/thread-chat-helpers.ts +18 -9
- package/src/runtime/thread-turn-context.ts +28 -74
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +169 -176
- package/src/services/agent-executor.service.ts +207 -196
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +16 -48
- package/src/services/autonomous-job.service.ts +81 -87
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +3 -1
- package/src/services/context-compaction.service.ts +8 -10
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +122 -109
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +68 -51
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +49 -15
- package/src/services/graph-full-routing.ts +49 -37
- package/src/services/index.ts +1 -0
- package/src/services/institutional-memory.service.ts +8 -17
- package/src/services/learned-skill.service.ts +38 -35
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +14 -3
- package/src/services/memory/memory-preseeded.ts +10 -4
- package/src/services/memory/memory-utils.ts +2 -1
- package/src/services/memory/memory.service.ts +37 -52
- package/src/services/memory/rerank.service.ts +3 -11
- package/src/services/monitoring-window.service.ts +1 -1
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +2 -2
- package/src/services/notification.service.ts +16 -4
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +34 -51
- package/src/services/ownership-dispatcher.service.ts +148 -95
- package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
- package/src/services/plan/plan-agent-query.service.ts +13 -9
- package/src/services/plan/plan-approval.service.ts +52 -48
- package/src/services/plan/plan-artifact.service.ts +2 -2
- package/src/services/plan/plan-builder.service.ts +2 -2
- package/src/services/plan/plan-checkpoint.service.ts +1 -1
- package/src/services/plan/plan-compiler.service.ts +1 -1
- package/src/services/plan/plan-completion-side-effects.ts +99 -113
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -202
- package/src/services/plan/plan-deadline.service.ts +304 -307
- package/src/services/plan/plan-event-delivery.service.ts +84 -72
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +375 -353
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +494 -489
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +89 -82
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +8 -5
- package/src/services/plan/plan-validator.service.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +17 -11
- package/src/services/plugin-executor.service.ts +26 -21
- package/src/services/quality-metrics.service.ts +1 -1
- package/src/services/queue-job.service.ts +8 -17
- package/src/services/recent-activity-title.service.ts +22 -10
- package/src/services/recent-activity.service.ts +1 -1
- package/src/services/skill-resolver.service.ts +1 -1
- package/src/services/social-chat-history.service.ts +37 -20
- package/src/services/system-executor.service.ts +25 -20
- package/src/services/thread/thread-bootstrap.ts +37 -19
- package/src/services/thread/thread-listing.ts +2 -1
- package/src/services/thread/thread-memory-block.ts +18 -5
- package/src/services/thread/thread-message.service.ts +30 -13
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +87 -83
- package/src/services/thread/thread-turn-preparation.service.ts +65 -40
- package/src/services/thread/thread-turn-streaming.ts +32 -36
- package/src/services/thread/thread-turn.ts +43 -29
- package/src/services/thread/thread.service.ts +32 -8
- package/src/services/user.service.ts +1 -1
- package/src/services/write-intent-validator.service.ts +1 -1
- package/src/storage/attachment-storage.service.ts +7 -4
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +1 -1
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +1 -1
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/system-agents/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +36 -20
- package/src/tools/fetch-webpage.tool.ts +30 -22
- package/src/tools/firecrawl-client.ts +1 -6
- package/src/tools/plan-approval.tool.ts +9 -1
- package/src/tools/remember-memory.tool.ts +3 -6
- package/src/tools/research-topic.tool.ts +12 -3
- package/src/tools/search-web.tool.ts +26 -18
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +34 -58
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +16 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +14 -8
- package/src/config/search.ts +0 -3
- package/src/effect/awaitable-effect.ts +0 -87
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -31
- package/src/redis/runtime-connection.ts +0 -10
- package/src/runtime/agent-types.ts +0 -1
|
@@ -33,7 +33,7 @@ export function makeSkillResolverService(deps: {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export class SkillResolverServiceTag extends Context.Service<SkillResolverServiceTag, SkillResolverService>()(
|
|
36
|
-
'SkillResolverService',
|
|
36
|
+
'@lota-sdk/core/SkillResolverService',
|
|
37
37
|
) {}
|
|
38
38
|
|
|
39
39
|
export const SkillResolverServiceLive = Layer.effect(
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { normalizeMessageBatch } from '@lota-sdk/shared'
|
|
2
|
-
import { Context, Effect, Layer } from 'effect'
|
|
2
|
+
import { Context, Effect, Layer, Schema } from 'effect'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
5
6
|
import { RedisServiceTag, RuntimeConfigServiceTag } from '../effect/services'
|
|
6
7
|
import type { RedisConnectionManager } from '../redis/connection'
|
|
7
8
|
import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
|
|
8
9
|
import type { LotaRuntimeBackgroundCursor, LotaRuntimeBackgroundCursorKind } from '../runtime/runtime-extensions'
|
|
9
10
|
|
|
11
|
+
export class SocialChatHistoryError extends Schema.TaggedErrorClass<SocialChatHistoryError>()(
|
|
12
|
+
'SocialChatHistoryError',
|
|
13
|
+
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
const tryRedisPromise = makeEffectTryPromiseWithMessage(
|
|
17
|
+
(message, cause) => new SocialChatHistoryError({ message, cause }),
|
|
18
|
+
)
|
|
19
|
+
|
|
10
20
|
const DEFAULT_SOCIAL_CHAT_HISTORY_PREFIX = 'lota:social:history'
|
|
11
21
|
|
|
12
22
|
const SocialChatMessageRoleSchema = z.enum(['user', 'assistant'])
|
|
@@ -57,6 +67,8 @@ const SocialChatHistoryMessageSchema = z.object({
|
|
|
57
67
|
metadata: SocialChatHistoryMetadataSchema.optional(),
|
|
58
68
|
cursor: z.object({ createdAt: z.coerce.date(), id: z.string().trim().min(1) }),
|
|
59
69
|
})
|
|
70
|
+
const SocialChatHistoryCursorSchema = z.object({ createdAt: z.coerce.date(), id: z.string().trim().min(1) })
|
|
71
|
+
const decodeJsonStringEffect = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)
|
|
60
72
|
|
|
61
73
|
export type SocialChatHistoryPart = z.infer<typeof SocialChatHistoryPartSchema>
|
|
62
74
|
export type SocialChatHistoryMetadata = z.infer<typeof SocialChatHistoryMetadataSchema>
|
|
@@ -101,7 +113,7 @@ export function makeSocialChatHistoryService(
|
|
|
101
113
|
const parseStoredMessageEffect = (value: string | null): Effect.Effect<SocialChatHistoryMessage | null> => {
|
|
102
114
|
if (!value) return Effect.succeed(null)
|
|
103
115
|
|
|
104
|
-
return
|
|
116
|
+
return decodeJsonStringEffect(value).pipe(
|
|
105
117
|
Effect.map((parsed) => {
|
|
106
118
|
const validated = SocialChatHistoryMessageSchema.safeParse(parsed)
|
|
107
119
|
return validated.success ? validated.data : null
|
|
@@ -116,9 +128,9 @@ export function makeSocialChatHistoryService(
|
|
|
116
128
|
const parseCursorEffect = (value: string | null): Effect.Effect<LotaRuntimeBackgroundCursor | null> => {
|
|
117
129
|
if (!value) return Effect.succeed(null)
|
|
118
130
|
|
|
119
|
-
return
|
|
131
|
+
return decodeJsonStringEffect(value).pipe(
|
|
120
132
|
Effect.map((parsed) => {
|
|
121
|
-
const validated =
|
|
133
|
+
const validated = SocialChatHistoryCursorSchema.safeParse(parsed)
|
|
122
134
|
return validated.success ? validated.data : null
|
|
123
135
|
}),
|
|
124
136
|
Effect.catch(() => Effect.succeed(null)),
|
|
@@ -145,19 +157,20 @@ export function makeSocialChatHistoryService(
|
|
|
145
157
|
multi.zadd(workspaceIndexKey(message.workspaceId), score, storageKey)
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
yield*
|
|
160
|
+
yield* tryRedisPromise(() => multi.exec(), 'Failed to upsert social chat messages.')
|
|
149
161
|
return normalizedMessages
|
|
150
162
|
})
|
|
151
163
|
|
|
152
164
|
const listThreadMessagesEffect = (params: { workspaceId: string; threadId: string }) =>
|
|
153
165
|
Effect.gen(function* () {
|
|
154
166
|
const conn = redis.getConnection()
|
|
155
|
-
const storageKeys = yield*
|
|
156
|
-
conn.zrange(threadIndexKey(params.workspaceId, params.threadId), 0, -1),
|
|
167
|
+
const storageKeys = yield* tryRedisPromise(
|
|
168
|
+
() => conn.zrange(threadIndexKey(params.workspaceId, params.threadId), 0, -1),
|
|
169
|
+
'Failed to list thread message keys.',
|
|
157
170
|
)
|
|
158
171
|
if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
|
|
159
172
|
|
|
160
|
-
const storedValues = yield*
|
|
173
|
+
const storedValues = yield* tryRedisPromise(() => conn.mget(storageKeys), 'Failed to read thread messages.')
|
|
161
174
|
const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
|
|
162
175
|
return parsedMessages
|
|
163
176
|
.filter((message): message is SocialChatHistoryMessage => message !== null)
|
|
@@ -175,14 +188,16 @@ export function makeSocialChatHistoryService(
|
|
|
175
188
|
const scoreStart =
|
|
176
189
|
params.cursor?.createdAt.getTime() ??
|
|
177
190
|
(params.onboardingCutoff ? params.onboardingCutoff.getTime() : Number.NEGATIVE_INFINITY)
|
|
178
|
-
const storageKeys = yield*
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
const storageKeys = yield* tryRedisPromise(
|
|
192
|
+
() =>
|
|
193
|
+
params.cursor || params.onboardingCutoff
|
|
194
|
+
? conn.zrangebyscore(indexKey, scoreStart, '+inf')
|
|
195
|
+
: conn.zrange(indexKey, 0, -1),
|
|
196
|
+
'Failed to list workspace message keys.',
|
|
182
197
|
)
|
|
183
198
|
if (storageKeys.length === 0) return [] as SocialChatHistoryMessage[]
|
|
184
199
|
|
|
185
|
-
const storedValues = yield*
|
|
200
|
+
const storedValues = yield* tryRedisPromise(() => conn.mget(storageKeys), 'Failed to read workspace messages.')
|
|
186
201
|
const parsedMessages = yield* Effect.forEach(storedValues, parseStoredMessageEffect)
|
|
187
202
|
return parsedMessages
|
|
188
203
|
.filter((message): message is SocialChatHistoryMessage => message !== null)
|
|
@@ -210,18 +225,20 @@ export function makeSocialChatHistoryService(
|
|
|
210
225
|
}).pipe(Effect.map((messages) => messages.length > 0))
|
|
211
226
|
|
|
212
227
|
const getBackgroundCursorEffect = (kind: LotaRuntimeBackgroundCursorKind, workspaceId: string) =>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
tryRedisPromise(
|
|
229
|
+
() => redis.getConnection().get(cursorKey(kind, workspaceId)),
|
|
230
|
+
'Failed to read background cursor.',
|
|
231
|
+
).pipe(Effect.flatMap((value) => parseCursorEffect(value)))
|
|
216
232
|
|
|
217
233
|
const setBackgroundCursorEffect = (
|
|
218
234
|
kind: LotaRuntimeBackgroundCursorKind,
|
|
219
235
|
workspaceId: string,
|
|
220
236
|
cursor: LotaRuntimeBackgroundCursor,
|
|
221
237
|
) =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
tryRedisPromise(
|
|
239
|
+
() => redis.getConnection().set(cursorKey(kind, workspaceId), serializeCursor(cursor)),
|
|
240
|
+
'Failed to write background cursor.',
|
|
241
|
+
).pipe(Effect.asVoid)
|
|
225
242
|
|
|
226
243
|
return {
|
|
227
244
|
upsertMessages: upsertMessagesEffect,
|
|
@@ -236,7 +253,7 @@ export function makeSocialChatHistoryService(
|
|
|
236
253
|
export class SocialChatHistoryServiceTag extends Context.Service<
|
|
237
254
|
SocialChatHistoryServiceTag,
|
|
238
255
|
ReturnType<typeof makeSocialChatHistoryService>
|
|
239
|
-
>()('SocialChatHistoryService') {}
|
|
256
|
+
>()('@lota-sdk/core/SocialChatHistoryService') {}
|
|
240
257
|
|
|
241
258
|
export const SocialChatHistoryServiceLive = Layer.effect(
|
|
242
259
|
SocialChatHistoryServiceTag,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlanNodeOwner } from '@lota-sdk/shared'
|
|
2
2
|
import { Context, Effect, Layer } from 'effect'
|
|
3
3
|
|
|
4
|
-
import { BadRequestError } from '../effect/errors'
|
|
4
|
+
import { BadRequestError, ServiceError } from '../effect/errors'
|
|
5
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
5
6
|
import { RuntimeConfigServiceTag } from '../effect/services'
|
|
7
|
+
|
|
8
|
+
const trySystemExecutorPromise = makeEffectTryPromiseWithMessage(
|
|
9
|
+
(message, cause) => new ServiceError({ message, cause }),
|
|
10
|
+
)
|
|
6
11
|
import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
|
|
7
12
|
import type { ResolvedLotaRuntimeConfig } from '../runtime/runtime-config'
|
|
8
13
|
import type { PlanValidationIssueInput } from './plan/plan-validator.service'
|
|
@@ -86,27 +91,27 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
|
|
|
86
91
|
return []
|
|
87
92
|
},
|
|
88
93
|
|
|
89
|
-
executeNode(params: {
|
|
94
|
+
executeNode: Effect.fn('SystemExecutor.executeNode')(function* (params: {
|
|
90
95
|
nodeSpec: PlanNodeSpec
|
|
91
96
|
resolvedInput: Record<string, unknown>
|
|
92
97
|
context: OwnershipDispatchContext
|
|
93
98
|
}) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
99
|
+
const owner = params.nodeSpec.owner
|
|
100
|
+
if (!isSystemOwner(owner)) {
|
|
101
|
+
return yield* new BadRequestError({
|
|
102
|
+
message: `SystemExecutor cannot execute owner type "${owner.executorType}".`,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const executor = getSystemExecutors()[owner.ref]
|
|
107
|
+
if (!executor || !executor.supportedOperations.includes(owner.operation)) {
|
|
108
|
+
return yield* new BadRequestError({
|
|
109
|
+
message: `System executor ${owner.ref}.${owner.operation} is not registered.`,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
return yield* trySystemExecutorPromise(
|
|
114
|
+
() =>
|
|
110
115
|
executor.executeNode(
|
|
111
116
|
buildSystemExecutionParams({
|
|
112
117
|
owner,
|
|
@@ -115,16 +120,16 @@ export function makeSystemExecutorService(config: ResolvedLotaRuntimeConfig) {
|
|
|
115
120
|
context: params.context,
|
|
116
121
|
}),
|
|
117
122
|
),
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
},
|
|
123
|
+
`System executor "${owner.ref}.${owner.operation}" failed.`,
|
|
124
|
+
).pipe(Effect.withSpan('SystemExecutor.invoke'))
|
|
125
|
+
}),
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
export class SystemExecutorServiceTag extends Context.Service<
|
|
125
130
|
SystemExecutorServiceTag,
|
|
126
131
|
ReturnType<typeof makeSystemExecutorService>
|
|
127
|
-
>()('SystemExecutorService') {}
|
|
132
|
+
>()('@lota-sdk/core/SystemExecutorService') {}
|
|
128
133
|
|
|
129
134
|
export const SystemExecutorServiceLive = Layer.effect(
|
|
130
135
|
SystemExecutorServiceTag,
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { THREAD } from '@lota-sdk/shared'
|
|
2
2
|
import { Effect } from 'effect'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import type { ResolvedAgentConfig } from '../../config/agent-defaults'
|
|
5
5
|
import { serverLogger } from '../../config/logger'
|
|
6
|
-
import {
|
|
6
|
+
import type { ResolvedThreadBootstrapConfig } from '../../config/thread-defaults'
|
|
7
7
|
import type { RecordIdRef } from '../../db/record-id'
|
|
8
8
|
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
9
9
|
import { TABLES } from '../../db/tables'
|
|
10
10
|
import { BadRequestError, DatabaseError } from '../../effect/errors'
|
|
11
|
+
import type { LockAcquisitionError, LockLostError, NotFoundError, RedisError, ServiceError } from '../../effect/errors'
|
|
11
12
|
import type { RedisConnectionManager } from '../../redis/connection'
|
|
12
13
|
import { withLeaseLock } from '../../redis/redis-lease-lock'
|
|
13
14
|
import type { makeThreadMessageService } from './thread-message.service'
|
|
@@ -19,8 +20,8 @@ const THREAD_BOOTSTRAP_LOCK_REFRESH_INTERVAL_MS = 5_000
|
|
|
19
20
|
const THREAD_BOOTSTRAP_LOCK_MAX_WAIT_MS = 5_000
|
|
20
21
|
const THREAD_BOOTSTRAP_LOCK_RETRY_DELAY_MS = 100
|
|
21
22
|
|
|
22
|
-
function getAgentDisplayName(agentId: string): string {
|
|
23
|
-
return
|
|
23
|
+
function getAgentDisplayName(agentConfig: ResolvedAgentConfig, agentId: string): string {
|
|
24
|
+
return agentConfig.displayNames[agentId] ?? agentId
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
function buildBootstrapThreadsLockKey(userId: RecordIdRef, orgId: RecordIdRef): string {
|
|
@@ -31,9 +32,26 @@ function haveSameMembers(left: string[], right: string[]): boolean {
|
|
|
31
32
|
return left.length === right.length && left.every((value, index) => value === right[index])
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
type
|
|
35
|
+
type ThreadNormalizationError = BadRequestError | ServiceError
|
|
36
|
+
type ThreadBootstrapError =
|
|
37
|
+
| BadRequestError
|
|
38
|
+
| DatabaseError
|
|
39
|
+
| LockAcquisitionError
|
|
40
|
+
| LockLostError
|
|
41
|
+
| NotFoundError
|
|
42
|
+
| RedisError
|
|
43
|
+
| ServiceError
|
|
44
|
+
|
|
45
|
+
type NormalizedThreadFactory = (thread: ThreadRecord) => Effect.Effect<NormalizedThread, ThreadNormalizationError>
|
|
46
|
+
type DuplicateCreateErrorLike = { cause?: unknown; message?: unknown }
|
|
47
|
+
|
|
48
|
+
function isDuplicateCreateErrorLike(value: unknown): value is DuplicateCreateErrorLike {
|
|
49
|
+
return (typeof value === 'object' || typeof value === 'function') && value !== null
|
|
50
|
+
}
|
|
35
51
|
|
|
36
52
|
export function createThreadBootstrapHelpers(deps: {
|
|
53
|
+
agentConfig: ResolvedAgentConfig
|
|
54
|
+
threadBootstrapConfig: ResolvedThreadBootstrapConfig
|
|
37
55
|
threadStore: ThreadRecordStore
|
|
38
56
|
threadMessageService: Pick<ReturnType<typeof makeThreadMessageService>, 'ensureBootstrapWelcomeMessageEffect'>
|
|
39
57
|
redis: RedisConnectionManager
|
|
@@ -85,26 +103,26 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
85
103
|
const seen = new Set<unknown>()
|
|
86
104
|
let current = error
|
|
87
105
|
|
|
88
|
-
while ((
|
|
106
|
+
while (isDuplicateCreateErrorLike(current) && !seen.has(current)) {
|
|
89
107
|
seen.add(current)
|
|
90
|
-
|
|
91
|
-
if (typeof message === 'string' && message.includes('already contains')) {
|
|
108
|
+
if (typeof current.message === 'string' && current.message.includes('already contains')) {
|
|
92
109
|
return true
|
|
93
110
|
}
|
|
94
|
-
current =
|
|
111
|
+
current = current.cause
|
|
95
112
|
}
|
|
96
113
|
|
|
97
114
|
return false
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
const normalizeThreadEffect = (thread: ThreadRecord) =>
|
|
117
|
+
const normalizeThreadEffect = (thread: ThreadRecord): Effect.Effect<NormalizedThread, ThreadNormalizationError> =>
|
|
118
|
+
deps.normalizeThread(thread)
|
|
101
119
|
|
|
102
120
|
const getOrCreateDefaultEffect = (
|
|
103
121
|
orgId: RecordIdRef,
|
|
104
122
|
userId: RecordIdRef,
|
|
105
123
|
agentId: string,
|
|
106
124
|
config?: { title?: string; nameGenerated?: boolean },
|
|
107
|
-
) => {
|
|
125
|
+
): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
|
|
108
126
|
const lookup = { type: 'default' as const, organizationId: orgId, userId, agentId }
|
|
109
127
|
|
|
110
128
|
return Effect.gen(function* () {
|
|
@@ -121,7 +139,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
121
139
|
userId,
|
|
122
140
|
agentId,
|
|
123
141
|
members: [agentId],
|
|
124
|
-
title: config?.title ?? getAgentDisplayName(agentId),
|
|
142
|
+
title: config?.title ?? getAgentDisplayName(deps.agentConfig, agentId),
|
|
125
143
|
status: 'active',
|
|
126
144
|
nameGenerated: config?.nameGenerated ?? false,
|
|
127
145
|
isCompacting: false,
|
|
@@ -150,7 +168,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
150
168
|
userId: RecordIdRef,
|
|
151
169
|
threadType: string,
|
|
152
170
|
config: { members: string[]; title: string; nameGenerated?: boolean },
|
|
153
|
-
) => {
|
|
171
|
+
): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
|
|
154
172
|
const lookup = { type: 'thread' as const, organizationId: orgId, userId, threadType }
|
|
155
173
|
|
|
156
174
|
return Effect.gen(function* () {
|
|
@@ -199,7 +217,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
199
217
|
threadType?: string
|
|
200
218
|
members?: string[]
|
|
201
219
|
title?: string
|
|
202
|
-
}) =>
|
|
220
|
+
}): Effect.Effect<NormalizedThread, ThreadBootstrapError> =>
|
|
203
221
|
Effect.gen(function* () {
|
|
204
222
|
switch (input.type) {
|
|
205
223
|
case 'default':
|
|
@@ -234,7 +252,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
234
252
|
const threadType = input.threadType
|
|
235
253
|
if (!threadType) return yield* new BadRequestError({ message: 'Thread threads require threadType' })
|
|
236
254
|
const { record } = yield* getOrCreateThreadEffect(input.organizationId, input.userId, threadType, {
|
|
237
|
-
members: input.members ?? [...
|
|
255
|
+
members: input.members ?? [...deps.agentConfig.roster],
|
|
238
256
|
title,
|
|
239
257
|
nameGenerated,
|
|
240
258
|
})
|
|
@@ -247,7 +265,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
247
265
|
type: input.type,
|
|
248
266
|
agentId: input.agentId,
|
|
249
267
|
threadType: input.threadType,
|
|
250
|
-
members: input.members ?? [...
|
|
268
|
+
members: input.members ?? [...deps.agentConfig.roster],
|
|
251
269
|
title,
|
|
252
270
|
status: 'active',
|
|
253
271
|
nameGenerated,
|
|
@@ -262,8 +280,8 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
262
280
|
userId: RecordIdRef,
|
|
263
281
|
orgId: RecordIdRef,
|
|
264
282
|
options?: { onboardStatus?: string; userName?: string | null },
|
|
265
|
-
) => {
|
|
266
|
-
const bootstrapConfig =
|
|
283
|
+
): Effect.Effect<void, ThreadBootstrapError> => {
|
|
284
|
+
const bootstrapConfig = deps.threadBootstrapConfig
|
|
267
285
|
|
|
268
286
|
return withLeaseLock(
|
|
269
287
|
{
|
|
@@ -326,7 +344,7 @@ export function createThreadBootstrapHelpers(deps: {
|
|
|
326
344
|
if (onboardingCompleted) {
|
|
327
345
|
for (const wsType of bootstrapConfig.threadTypesAfterOnboarding) {
|
|
328
346
|
if (threadThreadsByType.has(wsType)) continue
|
|
329
|
-
const profile = getCoreThreadProfile(wsType)
|
|
347
|
+
const profile = deps.agentConfig.getCoreThreadProfile(wsType)
|
|
330
348
|
yield* getOrCreateThreadEffect(orgId, userId, wsType, {
|
|
331
349
|
members: [...profile.members],
|
|
332
350
|
title: profile.config.title,
|
|
@@ -5,6 +5,7 @@ import { BoundQuery } from 'surrealdb'
|
|
|
5
5
|
import type { RecordIdRef } from '../../db/record-id'
|
|
6
6
|
import type { SurrealDBService } from '../../db/service'
|
|
7
7
|
import { TABLES } from '../../db/tables'
|
|
8
|
+
import type { BadRequestError, ServiceError } from '../../effect/errors'
|
|
8
9
|
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
9
10
|
import { ThreadSchema } from './thread.types'
|
|
10
11
|
import type { NormalizedThread, ThreadRecord } from './thread.types'
|
|
@@ -38,7 +39,7 @@ export function createThreadListingHelpers(deps: {
|
|
|
38
39
|
normalizeThreads(
|
|
39
40
|
threads: ThreadRecord[],
|
|
40
41
|
options?: { checkLease?: boolean },
|
|
41
|
-
): Effect.Effect<NormalizedThread[],
|
|
42
|
+
): Effect.Effect<NormalizedThread[], BadRequestError | ServiceError>
|
|
42
43
|
}) {
|
|
43
44
|
class ThreadListingError extends Schema.TaggedErrorClass<ThreadListingError>()('ThreadListingError', {
|
|
44
45
|
message: Schema.String,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Context } from 'effect'
|
|
1
2
|
import { Schema, Effect } from 'effect'
|
|
2
3
|
|
|
3
4
|
import { serverLogger } from '../../config/logger'
|
|
@@ -9,16 +10,19 @@ import {
|
|
|
9
10
|
appendToMemoryBlock,
|
|
10
11
|
compactMemoryBlockEntries,
|
|
11
12
|
formatPersistedMemoryBlockForPrompt,
|
|
13
|
+
MemoryBlockCompactError,
|
|
12
14
|
parseMemoryBlock,
|
|
13
15
|
serializeMemoryBlock,
|
|
14
16
|
} from '../../runtime/memory/memory-block'
|
|
17
|
+
import type { BackgroundWorkService } from '../background-work.service'
|
|
18
|
+
import type { makeContextCompactionService } from '../context-compaction.service'
|
|
15
19
|
import { MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES, MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES } from './thread-constants'
|
|
16
20
|
import type { ThreadRecordStore } from './thread-record-store'
|
|
17
21
|
import type { ThreadRecord } from './thread.types'
|
|
18
22
|
|
|
19
|
-
type ContextCompactionServiceLike =
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
type ContextCompactionServiceLike = Pick<ReturnType<typeof makeContextCompactionService>, 'compactMemoryBlock'>
|
|
24
|
+
|
|
25
|
+
type BackgroundWorker = Context.Service.Shape<typeof BackgroundWorkService>
|
|
22
26
|
|
|
23
27
|
class ThreadMemoryBlockError extends Schema.TaggedErrorClass<ThreadMemoryBlockError>()('ThreadMemoryBlockError', {
|
|
24
28
|
message: Schema.String,
|
|
@@ -39,6 +43,7 @@ export function formatMemoryBlockForPrompt(thread: Pick<ThreadRecord, 'memoryBlo
|
|
|
39
43
|
export function createThreadMemoryBlockHelpers(deps: {
|
|
40
44
|
threadStore: ThreadRecordStore
|
|
41
45
|
contextCompactionService: ContextCompactionServiceLike
|
|
46
|
+
background: BackgroundWorker
|
|
42
47
|
}) {
|
|
43
48
|
function appendMemoryBlock(threadId: RecordIdRef, entry: string): Effect.Effect<string, ThreadMemoryBlockError> {
|
|
44
49
|
return Effect.gen(function* () {
|
|
@@ -63,7 +68,7 @@ export function createThreadMemoryBlockHelpers(deps: {
|
|
|
63
68
|
)
|
|
64
69
|
|
|
65
70
|
if (updatedEntries.length >= MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES) {
|
|
66
|
-
yield*
|
|
71
|
+
yield* deps.background.run(
|
|
67
72
|
compactMemoryBlock(threadRef).pipe(
|
|
68
73
|
Effect.catch((error: unknown) =>
|
|
69
74
|
Effect.sync(() => {
|
|
@@ -71,6 +76,7 @@ export function createThreadMemoryBlockHelpers(deps: {
|
|
|
71
76
|
}),
|
|
72
77
|
),
|
|
73
78
|
),
|
|
79
|
+
'thread-memory-block.compactMemoryBlock',
|
|
74
80
|
)
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -93,7 +99,14 @@ export function createThreadMemoryBlockHelpers(deps: {
|
|
|
93
99
|
entries: parseMemoryBlock(thread.memoryBlock),
|
|
94
100
|
triggerEntries: MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES,
|
|
95
101
|
chunkEntries: MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES,
|
|
96
|
-
compact: (params) =>
|
|
102
|
+
compact: (params) =>
|
|
103
|
+
deps.contextCompactionService
|
|
104
|
+
.compactMemoryBlock(params)
|
|
105
|
+
.pipe(
|
|
106
|
+
Effect.mapError(
|
|
107
|
+
(cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause }),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
97
110
|
}),
|
|
98
111
|
`Failed to compact memory block for thread ${threadIdString}`,
|
|
99
112
|
)
|
|
@@ -3,8 +3,9 @@ import type { ChatMessage } from '@lota-sdk/shared'
|
|
|
3
3
|
import { Context, Effect, Layer } from 'effect'
|
|
4
4
|
import { RecordId, surql } from 'surrealdb'
|
|
5
5
|
import { z } from 'zod'
|
|
6
|
+
import type { ZodTypeAny } from 'zod'
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import type { ResolvedAgentConfig } from '../../config/agent-defaults'
|
|
8
9
|
import { CursorRowSchema, listMessageHistoryPageEffect } from '../../db/cursor-pagination'
|
|
9
10
|
import type { CursorPaginationConfig } from '../../db/cursor-pagination'
|
|
10
11
|
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
@@ -15,12 +16,23 @@ import { ThreadMessageRowSchema } from '../../db/thread-message-row'
|
|
|
15
16
|
import type { ThreadMessageRow } from '../../db/thread-message-row'
|
|
16
17
|
import { ServiceError } from '../../effect/errors'
|
|
17
18
|
import { effectTryServicePromise } from '../../effect/helpers'
|
|
18
|
-
import { DatabaseServiceTag } from '../../effect/services'
|
|
19
|
+
import { AgentConfigServiceTag, DatabaseServiceTag } from '../../effect/services'
|
|
19
20
|
import { sha256Hex } from '../../utils/crypto'
|
|
20
21
|
import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
|
|
21
22
|
|
|
22
23
|
const ThreadMessageExistingRowSchema = z.object({ id: recordIdSchema, createdAt: z.coerce.date() })
|
|
23
24
|
|
|
25
|
+
function parseRowOrFail<TSchema extends ZodTypeAny>(
|
|
26
|
+
schema: TSchema,
|
|
27
|
+
value: unknown,
|
|
28
|
+
operation: string,
|
|
29
|
+
): Effect.Effect<z.infer<TSchema>, ServiceError> {
|
|
30
|
+
return Effect.try({
|
|
31
|
+
try: () => schema.parse(value) as z.infer<TSchema>,
|
|
32
|
+
catch: (cause) => new ServiceError({ message: `Failed to parse row for ${operation}.`, cause }),
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
function toMessageId(value: string | RecordIdRef): string {
|
|
25
37
|
return recordIdToString(value, TABLES.THREAD_MESSAGE)
|
|
26
38
|
}
|
|
@@ -94,7 +106,7 @@ const threadPaginationConfig: CursorPaginationConfig = {
|
|
|
94
106
|
`,
|
|
95
107
|
}
|
|
96
108
|
|
|
97
|
-
export function makeThreadMessageService(db: SurrealDBService) {
|
|
109
|
+
export function makeThreadMessageService(db: SurrealDBService, agentConfig: ResolvedAgentConfig) {
|
|
98
110
|
function upsertMessages(params: { threadId: RecordIdRef; messages: ChatMessage[] }) {
|
|
99
111
|
const threadId = toThreadRef(params.threadId)
|
|
100
112
|
return Effect.forEach(params.messages, (message) =>
|
|
@@ -160,7 +172,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
|
|
|
160
172
|
`),
|
|
161
173
|
'Failed to list thread messages.',
|
|
162
174
|
).pipe(
|
|
163
|
-
Effect.
|
|
175
|
+
Effect.flatMap((rows) =>
|
|
176
|
+
Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listMessages')),
|
|
177
|
+
),
|
|
178
|
+
Effect.map((rows) => rows.map((row) => toChatMessage(row))),
|
|
164
179
|
)
|
|
165
180
|
},
|
|
166
181
|
listMessagesEffect(threadId: RecordIdRef) {
|
|
@@ -210,7 +225,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
|
|
|
210
225
|
`),
|
|
211
226
|
'Failed to list thread messages after cursor.',
|
|
212
227
|
)
|
|
213
|
-
|
|
228
|
+
const parsedRows = yield* Effect.forEach(rows, (row) =>
|
|
229
|
+
parseRowOrFail(ThreadMessageRowSchema, row, 'listMessagesAfterCursor'),
|
|
230
|
+
)
|
|
231
|
+
return parsedRows.map((row) => toChatMessage(row))
|
|
214
232
|
})
|
|
215
233
|
},
|
|
216
234
|
listMessagesAfterCursorEffect(threadId: RecordIdRef, afterMessageId?: string) {
|
|
@@ -229,12 +247,10 @@ export function makeThreadMessageService(db: SurrealDBService) {
|
|
|
229
247
|
`),
|
|
230
248
|
'Failed to list recent thread messages.',
|
|
231
249
|
).pipe(
|
|
232
|
-
Effect.
|
|
233
|
-
rows
|
|
234
|
-
.map((row) => ThreadMessageRowSchema.parse(row))
|
|
235
|
-
.reverse()
|
|
236
|
-
.map((row) => toChatMessage(row)),
|
|
250
|
+
Effect.flatMap((rows) =>
|
|
251
|
+
Effect.forEach(rows, (row) => parseRowOrFail(ThreadMessageRowSchema, row, 'listRecentMessages')),
|
|
237
252
|
),
|
|
253
|
+
Effect.map((rows) => rows.reverse().map((row) => toChatMessage(row))),
|
|
238
254
|
)
|
|
239
255
|
},
|
|
240
256
|
listRecentMessagesEffect(threadId: RecordIdRef, limit: number) {
|
|
@@ -333,7 +349,7 @@ export function makeThreadMessageService(db: SurrealDBService) {
|
|
|
333
349
|
parts: [{ type: 'text', text: messageText }],
|
|
334
350
|
metadata: {
|
|
335
351
|
agentId: params.agentId,
|
|
336
|
-
agentName:
|
|
352
|
+
agentName: agentConfig.displayNames[params.agentId] ?? params.agentId,
|
|
337
353
|
createdAt: nowEpochMillis(),
|
|
338
354
|
},
|
|
339
355
|
},
|
|
@@ -352,12 +368,13 @@ export function makeThreadMessageService(db: SurrealDBService) {
|
|
|
352
368
|
export class ThreadMessageServiceTag extends Context.Service<
|
|
353
369
|
ThreadMessageServiceTag,
|
|
354
370
|
ReturnType<typeof makeThreadMessageService>
|
|
355
|
-
>()('ThreadMessageService') {}
|
|
371
|
+
>()('@lota-sdk/core/ThreadMessageService') {}
|
|
356
372
|
|
|
357
373
|
export const ThreadMessageServiceLive = Layer.effect(
|
|
358
374
|
ThreadMessageServiceTag,
|
|
359
375
|
Effect.gen(function* () {
|
|
360
376
|
const db = yield* DatabaseServiceTag
|
|
361
|
-
|
|
377
|
+
const agentConfig = yield* AgentConfigServiceTag
|
|
378
|
+
return makeThreadMessageService(db, agentConfig)
|
|
362
379
|
}),
|
|
363
380
|
)
|
|
@@ -62,7 +62,7 @@ export function makeThreadTitleService(
|
|
|
62
62
|
export class ThreadTitleServiceTag extends Context.Service<
|
|
63
63
|
ThreadTitleServiceTag,
|
|
64
64
|
ReturnType<typeof makeThreadTitleService>
|
|
65
|
-
>()('ThreadTitleService') {}
|
|
65
|
+
>()('@lota-sdk/core/ThreadTitleService') {}
|
|
66
66
|
|
|
67
67
|
export const ThreadTitleServiceLive = Layer.effect(
|
|
68
68
|
ThreadTitleServiceTag,
|