@lota-sdk/core 0.4.8 → 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.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +96 -22
- package/src/ai-gateway/ai-gateway.ts +766 -223
- package/src/config/agent-defaults.ts +189 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/background-processing.ts +1 -1
- package/src/config/constants.ts +8 -2
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +299 -19
- package/src/config/thread-defaults.ts +40 -20
- package/src/create-runtime.ts +200 -449
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +868 -601
- package/src/db/memory.ts +396 -280
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +288 -0
- package/src/db/service.ts +912 -779
- package/src/db/startup.ts +153 -68
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +96 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +123 -0
- package/src/effect/index.ts +24 -0
- package/src/effect/layers.ts +238 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +46 -0
- package/src/effect/services.ts +61 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +128 -83
- package/src/index.ts +48 -1
- package/src/openrouter/direct-provider.ts +11 -35
- package/src/queues/autonomous-job.queue.ts +117 -73
- package/src/queues/context-compaction.queue.ts +50 -17
- package/src/queues/delayed-node-promotion.queue.ts +46 -17
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +26 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
- package/src/queues/plan-scheduler.queue.ts +97 -33
- package/src/queues/post-chat-memory.queue.ts +56 -26
- package/src/queues/queue-factory.ts +227 -59
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +45 -11
- package/src/redis/connection.ts +182 -113
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +20 -0
- package/src/redis/stream-context.ts +92 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +5 -2
- package/src/runtime/agent-stream-helpers.ts +24 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +16 -4
- package/src/runtime/helper-model.ts +139 -48
- package/src/runtime/index.ts +7 -8
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/memory/memory-scope.ts +53 -0
- package/src/runtime/plugin-resolution.ts +124 -25
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +177 -130
- package/src/runtime/retrieval-adapters.ts +40 -6
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +150 -61
- package/src/runtime/runtime-extensions.ts +23 -25
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +386 -0
- package/src/runtime/runtime-token.ts +47 -0
- package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
- package/src/runtime/social-chat/social-chat.ts +630 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +183 -111
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +253 -149
- package/src/services/artifact.service.ts +231 -149
- package/src/services/attachment.service.ts +171 -115
- package/src/services/autonomous-job.service.ts +890 -491
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +13 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +151 -88
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +278 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +101 -168
- package/src/services/graph-full-routing.ts +193 -0
- package/src/services/index.ts +19 -21
- package/src/services/institutional-memory.service.ts +213 -125
- package/src/services/learned-skill.service.ts +368 -260
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +50 -0
- package/src/services/memory/memory-preseeded.ts +86 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
- package/src/services/memory/memory.service.ts +674 -0
- package/src/services/memory/rerank.service.ts +201 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +29 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +153 -77
- package/src/services/ownership-dispatcher.service.ts +456 -263
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +169 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +405 -0
- package/src/services/plan/plan-deadline.service.ts +533 -0
- package/src/services/plan/plan-event-delivery.service.ts +266 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +522 -0
- package/src/services/plan/plan-executor-helpers.ts +307 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1737 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +637 -0
- package/src/services/plan/plan-scheduler.service.ts +379 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +36 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +131 -0
- package/src/services/plugin-executor.service.ts +102 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +288 -231
- package/src/services/recent-activity-title.service.ts +73 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +190 -122
- package/src/services/system-executor.service.ts +96 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +385 -0
- package/src/services/thread/thread-listing.ts +199 -0
- package/src/services/thread/thread-memory-block.ts +130 -0
- package/src/services/thread/thread-message.service.ts +379 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
- package/src/services/thread/thread-turn-streaming.ts +403 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +376 -0
- package/src/services/thread/thread.service.ts +344 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +334 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +3 -3
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +3 -3
- package/src/system-agents/memory.agent.ts +3 -3
- package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
- package/src/system-agents/skill-extractor.agent.ts +3 -3
- package/src/system-agents/skill-manager.agent.ts +3 -3
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +3 -3
- package/src/tools/execution-plan.tool.ts +241 -171
- package/src/tools/fetch-webpage.tool.ts +29 -18
- package/src/tools/firecrawl-client.ts +26 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +57 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +33 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +125 -84
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +25 -22
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +111 -20
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +164 -52
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +74 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/config/search.ts +0 -3
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/agent-types.ts +0 -1
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/memory-scope.ts +0 -43
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -914
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/rerank.service.ts +0 -156
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { surql } from 'surrealdb'
|
|
3
|
+
|
|
4
|
+
import { serverLogger } from '../../config/logger'
|
|
5
|
+
import type { RecordIdRef } from '../../db/record-id'
|
|
6
|
+
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
7
|
+
import type { SurrealDBService } from '../../db/service'
|
|
8
|
+
import { TABLES } from '../../db/tables'
|
|
9
|
+
import { ActiveThreadRunConflictError, DatabaseError, RedisError } from '../../effect/errors'
|
|
10
|
+
import type { RedisConnectionManager } from '../../redis/connection'
|
|
11
|
+
import { withLeaseLock } from '../../redis/redis-lease-lock'
|
|
12
|
+
import type { ThreadRecordStore } from './thread-record-store'
|
|
13
|
+
|
|
14
|
+
// Background turns can spend several seconds inside model/tool orchestration even
|
|
15
|
+
// when the process is healthy. A 5s lease was short enough to lose legitimate
|
|
16
|
+
// runs under normal jitter, so keep a wider safety margin while still expiring
|
|
17
|
+
// crashed runs quickly enough for recovery.
|
|
18
|
+
const THREAD_ACTIVE_RUN_LOCK_TTL_MS = 30_000
|
|
19
|
+
const THREAD_ACTIVE_RUN_LOCK_MAX_WAIT_MS = 750
|
|
20
|
+
const THREAD_ACTIVE_RUN_LOCK_RETRY_DELAY_MS = 75
|
|
21
|
+
const THREAD_ACTIVE_RUN_LOCK_REFRESH_INTERVAL_MS = 5_000
|
|
22
|
+
|
|
23
|
+
type ChatRunRegistryLike = {
|
|
24
|
+
has(runId: string): boolean
|
|
25
|
+
stopEffect(runId: string, reason: DOMException): Effect.Effect<boolean>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildActiveRunLockKey(threadId: RecordIdRef): string {
|
|
29
|
+
return `thread-active-run:${recordIdToString(ensureRecordId(threadId, TABLES.THREAD), TABLES.THREAD)}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeActiveTurnValue(value: unknown): string | null {
|
|
33
|
+
if (typeof value !== 'string') {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalized = value.trim()
|
|
38
|
+
return normalized.length > 0 ? normalized : null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createThreadActiveRunHelpers(deps: {
|
|
42
|
+
db: SurrealDBService
|
|
43
|
+
threadStore: ThreadRecordStore
|
|
44
|
+
redis: RedisConnectionManager
|
|
45
|
+
chatRunRegistry: ChatRunRegistryLike
|
|
46
|
+
}) {
|
|
47
|
+
const setActiveTurnEffect = (threadId: RecordIdRef, runId: string, streamId?: string | null) =>
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
50
|
+
if (streamId === null || streamId === undefined) {
|
|
51
|
+
yield* deps.db
|
|
52
|
+
.query(
|
|
53
|
+
surql`
|
|
54
|
+
UPDATE ONLY ${threadRef}
|
|
55
|
+
SET activeRunId = ${runId},
|
|
56
|
+
activeStreamId = NONE
|
|
57
|
+
`,
|
|
58
|
+
)
|
|
59
|
+
.pipe(Effect.mapError((cause) => new DatabaseError({ message: 'Failed to set active run state.', cause })))
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
yield* deps.db
|
|
64
|
+
.query(
|
|
65
|
+
surql`
|
|
66
|
+
UPDATE ONLY ${threadRef}
|
|
67
|
+
SET activeRunId = ${runId},
|
|
68
|
+
activeStreamId = ${streamId}
|
|
69
|
+
`,
|
|
70
|
+
)
|
|
71
|
+
.pipe(Effect.mapError((cause) => new DatabaseError({ message: 'Failed to set active run state.', cause })))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const getActiveTurnEffect = (threadId: RecordIdRef) =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
const thread = yield* deps.threadStore
|
|
77
|
+
.getById(threadId)
|
|
78
|
+
.pipe(Effect.mapError((cause) => new DatabaseError({ message: 'Failed to load active thread state.', cause })))
|
|
79
|
+
return {
|
|
80
|
+
runId: normalizeActiveTurnValue(thread.activeRunId),
|
|
81
|
+
streamId: normalizeActiveTurnValue(thread.activeStreamId),
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const getActiveRunIdEffect = (threadId: RecordIdRef) =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const { runId } = yield* getActiveTurnEffect(threadId)
|
|
88
|
+
return runId
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const hasActiveRunLeaseEffect = (threadId: RecordIdRef) =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
const count = yield* Effect.tryPromise({
|
|
94
|
+
try: () => deps.redis.getConnection().exists(buildActiveRunLockKey(threadId)),
|
|
95
|
+
catch: (error) => new RedisError({ message: 'Failed to inspect active run lease.', cause: error }),
|
|
96
|
+
})
|
|
97
|
+
return count > 0
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const getActiveStreamIdEffect = (threadId: RecordIdRef) =>
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
const { streamId } = yield* getActiveTurnEffect(threadId)
|
|
103
|
+
return streamId
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const clearActiveTurnEffect = (threadId: RecordIdRef, params: { runId: string; streamId?: string | null }) =>
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
109
|
+
const currentStreamId = params.streamId ?? null
|
|
110
|
+
if (currentStreamId === null) {
|
|
111
|
+
yield* deps.db
|
|
112
|
+
.query(
|
|
113
|
+
surql`UPDATE ONLY ${threadRef} SET activeRunId = NONE, activeStreamId = NONE WHERE activeRunId = ${params.runId}`,
|
|
114
|
+
)
|
|
115
|
+
.pipe(Effect.mapError((cause) => new DatabaseError({ message: 'Failed to clear active run state.', cause })))
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
yield* deps.db
|
|
120
|
+
.query(surql`
|
|
121
|
+
UPDATE ONLY ${threadRef}
|
|
122
|
+
SET activeRunId = NONE,
|
|
123
|
+
activeStreamId = NONE
|
|
124
|
+
WHERE activeRunId = ${params.runId} AND activeStreamId = ${currentStreamId}
|
|
125
|
+
`)
|
|
126
|
+
.pipe(Effect.mapError((cause) => new DatabaseError({ message: 'Failed to clear active run state.', cause })))
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const clearStaleActiveRunIfMissingFromRegistryEffect = (threadId: RecordIdRef) =>
|
|
130
|
+
Effect.gen(function* () {
|
|
131
|
+
const { runId: activeRunId, streamId: activeStreamId } = yield* getActiveTurnEffect(threadId)
|
|
132
|
+
if (!activeRunId) {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const hasLease = yield* hasActiveRunLeaseEffect(threadId)
|
|
137
|
+
if (hasLease) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
yield* clearActiveTurnEffect(threadId, { runId: activeRunId, streamId: activeStreamId })
|
|
142
|
+
serverLogger.warn`Cleared stale thread run after lease expired: thread=${recordIdToString(ensureRecordId(threadId, TABLES.THREAD), TABLES.THREAD)} run=${activeRunId}`
|
|
143
|
+
return true
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const stopActiveRunEffect = (threadId: RecordIdRef) =>
|
|
147
|
+
Effect.gen(function* () {
|
|
148
|
+
const { runId: activeRunId } = yield* getActiveTurnEffect(threadId)
|
|
149
|
+
if (!activeRunId) return false
|
|
150
|
+
|
|
151
|
+
const stopped = yield* deps.chatRunRegistry.stopEffect(
|
|
152
|
+
activeRunId,
|
|
153
|
+
new DOMException('Run stopped by user.', 'AbortError'),
|
|
154
|
+
)
|
|
155
|
+
if (stopped) {
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return yield* clearStaleActiveRunIfMissingFromRegistryEffect(threadId)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
function withActiveRunLease<T, E, R>(
|
|
163
|
+
threadId: RecordIdRef,
|
|
164
|
+
fn: (signal: AbortSignal) => Effect.Effect<T, E, R>,
|
|
165
|
+
): Effect.Effect<T, E | ActiveThreadRunConflictError | RedisError, R> {
|
|
166
|
+
return withLeaseLock(
|
|
167
|
+
{
|
|
168
|
+
redis: deps.redis.getConnection(),
|
|
169
|
+
lockKey: buildActiveRunLockKey(threadId),
|
|
170
|
+
lockTtlMs: THREAD_ACTIVE_RUN_LOCK_TTL_MS,
|
|
171
|
+
refreshIntervalMs: THREAD_ACTIVE_RUN_LOCK_REFRESH_INTERVAL_MS,
|
|
172
|
+
retryDelayMs: THREAD_ACTIVE_RUN_LOCK_RETRY_DELAY_MS,
|
|
173
|
+
maxWaitMs: THREAD_ACTIVE_RUN_LOCK_MAX_WAIT_MS,
|
|
174
|
+
label: 'thread active run',
|
|
175
|
+
logger: serverLogger,
|
|
176
|
+
},
|
|
177
|
+
fn,
|
|
178
|
+
).pipe(
|
|
179
|
+
Effect.catchTag('LockAcquisitionError', () =>
|
|
180
|
+
Effect.gen(function* () {
|
|
181
|
+
const activeRunId = (yield* Effect.catch(getActiveRunIdEffect(threadId), () => Effect.succeed(null))) ?? ''
|
|
182
|
+
return yield* new ActiveThreadRunConflictError({
|
|
183
|
+
threadId: recordIdToString(ensureRecordId(threadId, TABLES.THREAD), TABLES.THREAD),
|
|
184
|
+
activeRunId,
|
|
185
|
+
message: 'A chat run is already active.',
|
|
186
|
+
})
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
setActiveTurn: setActiveTurnEffect,
|
|
194
|
+
getActiveTurn: getActiveTurnEffect,
|
|
195
|
+
getActiveRunId: getActiveRunIdEffect,
|
|
196
|
+
hasActiveRunLease: hasActiveRunLeaseEffect,
|
|
197
|
+
withActiveRunLease,
|
|
198
|
+
getActiveStreamId: getActiveStreamIdEffect,
|
|
199
|
+
clearActiveTurn: clearActiveTurnEffect,
|
|
200
|
+
clearStaleActiveRunIfMissingFromRegistry: clearStaleActiveRunIfMissingFromRegistryEffect,
|
|
201
|
+
stopActiveRun: stopActiveRunEffect,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { THREAD } from '@lota-sdk/shared'
|
|
2
|
+
import { Effect } from 'effect'
|
|
3
|
+
|
|
4
|
+
import { getAgentDisplayNames, getAgentRoster, getCoreThreadProfile } from '../../config/agent-defaults'
|
|
5
|
+
import { serverLogger } from '../../config/logger'
|
|
6
|
+
import { getThreadBootstrapConfig } from '../../config/thread-defaults'
|
|
7
|
+
import type { RecordIdRef } from '../../db/record-id'
|
|
8
|
+
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
9
|
+
import { TABLES } from '../../db/tables'
|
|
10
|
+
import { BadRequestError, DatabaseError } from '../../effect/errors'
|
|
11
|
+
import type { LockAcquisitionError, LockLostError, NotFoundError, RedisError, ServiceError } from '../../effect/errors'
|
|
12
|
+
import type { RedisConnectionManager } from '../../redis/connection'
|
|
13
|
+
import { withLeaseLock } from '../../redis/redis-lease-lock'
|
|
14
|
+
import type { makeThreadMessageService } from './thread-message.service'
|
|
15
|
+
import type { ThreadRecordStore } from './thread-record-store'
|
|
16
|
+
import type { NormalizedThread, ThreadRecord } from './thread.types'
|
|
17
|
+
|
|
18
|
+
const THREAD_BOOTSTRAP_LOCK_TTL_MS = 15_000
|
|
19
|
+
const THREAD_BOOTSTRAP_LOCK_REFRESH_INTERVAL_MS = 5_000
|
|
20
|
+
const THREAD_BOOTSTRAP_LOCK_MAX_WAIT_MS = 5_000
|
|
21
|
+
const THREAD_BOOTSTRAP_LOCK_RETRY_DELAY_MS = 100
|
|
22
|
+
|
|
23
|
+
function getAgentDisplayName(agentId: string): string {
|
|
24
|
+
return getAgentDisplayNames()[agentId] ?? agentId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildBootstrapThreadsLockKey(userId: RecordIdRef, orgId: RecordIdRef): string {
|
|
28
|
+
return `thread-bootstrap:${recordIdToString(ensureRecordId(userId, TABLES.USER), TABLES.USER)}:${recordIdToString(ensureRecordId(orgId, TABLES.ORGANIZATION), TABLES.ORGANIZATION)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function haveSameMembers(left: string[], right: string[]): boolean {
|
|
32
|
+
return left.length === right.length && left.every((value, index) => value === right[index])
|
|
33
|
+
}
|
|
34
|
+
|
|
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
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createThreadBootstrapHelpers(deps: {
|
|
53
|
+
threadStore: ThreadRecordStore
|
|
54
|
+
threadMessageService: Pick<ReturnType<typeof makeThreadMessageService>, 'ensureBootstrapWelcomeMessageEffect'>
|
|
55
|
+
redis: RedisConnectionManager
|
|
56
|
+
normalizeThread: NormalizedThreadFactory
|
|
57
|
+
}) {
|
|
58
|
+
const syncThreadConfigEffect = (
|
|
59
|
+
record: ThreadRecord,
|
|
60
|
+
config?: { title?: string; nameGenerated?: boolean; members?: string[] },
|
|
61
|
+
) => {
|
|
62
|
+
if (!config) {
|
|
63
|
+
return Effect.succeed(record)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const updates: Partial<ThreadRecord> = {}
|
|
67
|
+
if (config.title !== undefined && record.title !== config.title) {
|
|
68
|
+
updates.title = config.title
|
|
69
|
+
}
|
|
70
|
+
if (config.nameGenerated !== undefined && record.nameGenerated !== config.nameGenerated) {
|
|
71
|
+
updates.nameGenerated = config.nameGenerated
|
|
72
|
+
}
|
|
73
|
+
if (config.members !== undefined && !haveSameMembers(record.members, config.members)) {
|
|
74
|
+
updates.members = config.members
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (Object.keys(updates).length === 0) {
|
|
78
|
+
return Effect.succeed(record)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return deps.threadStore.update(record.id, updates)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const findThreadByUniqueLookupEffect = (lookup: {
|
|
85
|
+
type: 'default' | 'thread'
|
|
86
|
+
organizationId: RecordIdRef
|
|
87
|
+
userId: RecordIdRef
|
|
88
|
+
agentId?: string
|
|
89
|
+
threadType?: string
|
|
90
|
+
}) => deps.threadStore.findThreadByUniqueLookup(lookup)
|
|
91
|
+
|
|
92
|
+
const waitForExistingThreadEffect = (lookup: {
|
|
93
|
+
type: 'default' | 'thread'
|
|
94
|
+
organizationId: RecordIdRef
|
|
95
|
+
userId: RecordIdRef
|
|
96
|
+
agentId?: string
|
|
97
|
+
threadType?: string
|
|
98
|
+
}) => deps.threadStore.waitForExistingThread(lookup)
|
|
99
|
+
|
|
100
|
+
const isDuplicateCreateError = (error: unknown): boolean => {
|
|
101
|
+
const seen = new Set<unknown>()
|
|
102
|
+
let current = error
|
|
103
|
+
|
|
104
|
+
while (isDuplicateCreateErrorLike(current) && !seen.has(current)) {
|
|
105
|
+
seen.add(current)
|
|
106
|
+
if (typeof current.message === 'string' && current.message.includes('already contains')) {
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
current = current.cause
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const normalizeThreadEffect = (thread: ThreadRecord): Effect.Effect<NormalizedThread, ThreadNormalizationError> =>
|
|
116
|
+
deps.normalizeThread(thread)
|
|
117
|
+
|
|
118
|
+
const getOrCreateDefaultEffect = (
|
|
119
|
+
orgId: RecordIdRef,
|
|
120
|
+
userId: RecordIdRef,
|
|
121
|
+
agentId: string,
|
|
122
|
+
config?: { title?: string; nameGenerated?: boolean },
|
|
123
|
+
): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
|
|
124
|
+
const lookup = { type: 'default' as const, organizationId: orgId, userId, agentId }
|
|
125
|
+
|
|
126
|
+
return Effect.gen(function* () {
|
|
127
|
+
const existing = yield* findThreadByUniqueLookupEffect(lookup)
|
|
128
|
+
if (existing) {
|
|
129
|
+
const record = yield* syncThreadConfigEffect(existing, config)
|
|
130
|
+
return { record, created: false as const }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return yield* Effect.matchEffect(
|
|
134
|
+
deps.threadStore.create({
|
|
135
|
+
type: 'default',
|
|
136
|
+
organizationId: orgId,
|
|
137
|
+
userId,
|
|
138
|
+
agentId,
|
|
139
|
+
members: [agentId],
|
|
140
|
+
title: config?.title ?? getAgentDisplayName(agentId),
|
|
141
|
+
status: 'active',
|
|
142
|
+
nameGenerated: config?.nameGenerated ?? false,
|
|
143
|
+
isCompacting: false,
|
|
144
|
+
turnCount: 0,
|
|
145
|
+
}),
|
|
146
|
+
{
|
|
147
|
+
onFailure: (error) => {
|
|
148
|
+
if (!isDuplicateCreateError(error)) {
|
|
149
|
+
return Effect.fail(error)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return Effect.gen(function* () {
|
|
153
|
+
const existingThread = yield* waitForExistingThreadEffect(lookup)
|
|
154
|
+
const record = yield* syncThreadConfigEffect(existingThread, config)
|
|
155
|
+
return { record, created: false as const }
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
onSuccess: (record) => Effect.succeed({ record, created: true as const }),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const getOrCreateThreadEffect = (
|
|
165
|
+
orgId: RecordIdRef,
|
|
166
|
+
userId: RecordIdRef,
|
|
167
|
+
threadType: string,
|
|
168
|
+
config: { members: string[]; title: string; nameGenerated?: boolean },
|
|
169
|
+
): Effect.Effect<{ created: boolean; record: ThreadRecord }, DatabaseError | NotFoundError> => {
|
|
170
|
+
const lookup = { type: 'thread' as const, organizationId: orgId, userId, threadType }
|
|
171
|
+
|
|
172
|
+
return Effect.gen(function* () {
|
|
173
|
+
const existing = yield* findThreadByUniqueLookupEffect(lookup)
|
|
174
|
+
if (existing) {
|
|
175
|
+
const record = yield* syncThreadConfigEffect(existing, config)
|
|
176
|
+
return { record, created: false as const }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return yield* Effect.matchEffect(
|
|
180
|
+
deps.threadStore.create({
|
|
181
|
+
type: 'thread',
|
|
182
|
+
organizationId: orgId,
|
|
183
|
+
userId,
|
|
184
|
+
threadType,
|
|
185
|
+
members: config.members,
|
|
186
|
+
title: config.title,
|
|
187
|
+
status: 'active',
|
|
188
|
+
nameGenerated: config.nameGenerated ?? false,
|
|
189
|
+
isCompacting: false,
|
|
190
|
+
turnCount: 0,
|
|
191
|
+
}),
|
|
192
|
+
{
|
|
193
|
+
onFailure: (error) => {
|
|
194
|
+
if (!isDuplicateCreateError(error)) {
|
|
195
|
+
return Effect.fail(error)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return Effect.gen(function* () {
|
|
199
|
+
const existingThread = yield* waitForExistingThreadEffect(lookup)
|
|
200
|
+
const record = yield* syncThreadConfigEffect(existingThread, config)
|
|
201
|
+
return { record, created: false as const }
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
onSuccess: (record) => Effect.succeed({ record, created: true as const }),
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const createThreadEffect = (input: {
|
|
211
|
+
userId: RecordIdRef
|
|
212
|
+
organizationId: RecordIdRef
|
|
213
|
+
type: string
|
|
214
|
+
agentId?: string
|
|
215
|
+
threadType?: string
|
|
216
|
+
members?: string[]
|
|
217
|
+
title?: string
|
|
218
|
+
}): Effect.Effect<NormalizedThread, ThreadBootstrapError> =>
|
|
219
|
+
Effect.gen(function* () {
|
|
220
|
+
switch (input.type) {
|
|
221
|
+
case 'default':
|
|
222
|
+
if (!input.agentId) return yield* new BadRequestError({ message: 'Default threads require agentId' })
|
|
223
|
+
if (input.threadType) return yield* new BadRequestError({ message: 'Default threads cannot have threadType' })
|
|
224
|
+
break
|
|
225
|
+
case 'topic':
|
|
226
|
+
if (!input.agentId) return yield* new BadRequestError({ message: 'Topic threads require agentId' })
|
|
227
|
+
if (input.threadType) return yield* new BadRequestError({ message: 'Topic threads cannot have threadType' })
|
|
228
|
+
break
|
|
229
|
+
case 'thread':
|
|
230
|
+
if (!input.threadType) return yield* new BadRequestError({ message: 'Thread threads require threadType' })
|
|
231
|
+
if (input.agentId) return yield* new BadRequestError({ message: 'Thread threads cannot have agentId' })
|
|
232
|
+
break
|
|
233
|
+
case 'group':
|
|
234
|
+
if (input.agentId) return yield* new BadRequestError({ message: 'Group threads cannot have agentId' })
|
|
235
|
+
if (input.threadType) return yield* new BadRequestError({ message: 'Group threads cannot have threadType' })
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const title = input.title ?? THREAD.DEFAULT_TITLE
|
|
240
|
+
const nameGenerated = input.title !== undefined && input.title !== THREAD.DEFAULT_TITLE
|
|
241
|
+
|
|
242
|
+
if (input.type === 'default') {
|
|
243
|
+
const agentId = input.agentId
|
|
244
|
+
if (!agentId) return yield* new BadRequestError({ message: 'Default threads require agentId' })
|
|
245
|
+
const { record } = yield* getOrCreateDefaultEffect(input.organizationId, input.userId, agentId, undefined)
|
|
246
|
+
return yield* normalizeThreadEffect(record)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (input.type === 'thread') {
|
|
250
|
+
const threadType = input.threadType
|
|
251
|
+
if (!threadType) return yield* new BadRequestError({ message: 'Thread threads require threadType' })
|
|
252
|
+
const { record } = yield* getOrCreateThreadEffect(input.organizationId, input.userId, threadType, {
|
|
253
|
+
members: input.members ?? [...getAgentRoster()],
|
|
254
|
+
title,
|
|
255
|
+
nameGenerated,
|
|
256
|
+
})
|
|
257
|
+
return yield* normalizeThreadEffect(record)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const thread = yield* deps.threadStore.create({
|
|
261
|
+
userId: input.userId,
|
|
262
|
+
organizationId: input.organizationId,
|
|
263
|
+
type: input.type,
|
|
264
|
+
agentId: input.agentId,
|
|
265
|
+
threadType: input.threadType,
|
|
266
|
+
members: input.members ?? [...getAgentRoster()],
|
|
267
|
+
title,
|
|
268
|
+
status: 'active',
|
|
269
|
+
nameGenerated,
|
|
270
|
+
isCompacting: false,
|
|
271
|
+
turnCount: 0,
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
return yield* normalizeThreadEffect(thread)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const ensureBootstrapThreadsEffect = (
|
|
278
|
+
userId: RecordIdRef,
|
|
279
|
+
orgId: RecordIdRef,
|
|
280
|
+
options?: { onboardStatus?: string; userName?: string | null },
|
|
281
|
+
): Effect.Effect<void, ThreadBootstrapError> => {
|
|
282
|
+
const bootstrapConfig = getThreadBootstrapConfig()
|
|
283
|
+
|
|
284
|
+
return withLeaseLock(
|
|
285
|
+
{
|
|
286
|
+
redis: deps.redis.getConnection(),
|
|
287
|
+
lockKey: buildBootstrapThreadsLockKey(userId, orgId),
|
|
288
|
+
lockTtlMs: THREAD_BOOTSTRAP_LOCK_TTL_MS,
|
|
289
|
+
refreshIntervalMs: THREAD_BOOTSTRAP_LOCK_REFRESH_INTERVAL_MS,
|
|
290
|
+
retryDelayMs: THREAD_BOOTSTRAP_LOCK_RETRY_DELAY_MS,
|
|
291
|
+
maxWaitMs: THREAD_BOOTSTRAP_LOCK_MAX_WAIT_MS,
|
|
292
|
+
label: 'thread bootstrap',
|
|
293
|
+
logger: serverLogger,
|
|
294
|
+
},
|
|
295
|
+
(signal) =>
|
|
296
|
+
Effect.gen(function* () {
|
|
297
|
+
const failIfAborted = () =>
|
|
298
|
+
signal.aborted
|
|
299
|
+
? Effect.fail(new DatabaseError({ message: 'Thread bootstrap lease was aborted.', cause: signal.reason }))
|
|
300
|
+
: Effect.void
|
|
301
|
+
|
|
302
|
+
yield* failIfAborted()
|
|
303
|
+
const onboardStatus = options?.onboardStatus ?? 'completed'
|
|
304
|
+
const onboardingCompleted = onboardStatus === 'completed'
|
|
305
|
+
|
|
306
|
+
const existingThreads = yield* deps.threadStore.findAll({ userId, organizationId: orgId })
|
|
307
|
+
yield* failIfAborted()
|
|
308
|
+
|
|
309
|
+
const hasGroupThread = existingThreads.some((t) => t.type === 'group')
|
|
310
|
+
const defaultThreadsByAgent = new Map<string, ThreadRecord>()
|
|
311
|
+
const threadThreadsByType = new Map<string, ThreadRecord>()
|
|
312
|
+
|
|
313
|
+
for (const thread of existingThreads) {
|
|
314
|
+
if (thread.type === 'default' && thread.agentId) {
|
|
315
|
+
defaultThreadsByAgent.set(thread.agentId, thread)
|
|
316
|
+
}
|
|
317
|
+
if (thread.type === 'thread' && typeof thread.threadType === 'string') {
|
|
318
|
+
threadThreadsByType.set(thread.threadType, thread)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const requiredDefaultAgents = onboardingCompleted
|
|
323
|
+
? bootstrapConfig.completedDefaultAgents
|
|
324
|
+
: bootstrapConfig.onboardingDefaultAgents
|
|
325
|
+
|
|
326
|
+
for (const agentId of requiredDefaultAgents) {
|
|
327
|
+
if (defaultThreadsByAgent.has(agentId)) continue
|
|
328
|
+
yield* getOrCreateDefaultEffect(orgId, userId, agentId)
|
|
329
|
+
yield* failIfAborted()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (onboardingCompleted && bootstrapConfig.ensureDefaultGroupOnCompleted && !hasGroupThread) {
|
|
333
|
+
const normalized = yield* createThreadEffect({
|
|
334
|
+
userId,
|
|
335
|
+
organizationId: orgId,
|
|
336
|
+
type: 'group',
|
|
337
|
+
title: THREAD.DEFAULT_TITLE,
|
|
338
|
+
})
|
|
339
|
+
yield* deps.threadStore.getById(normalized.id)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (onboardingCompleted) {
|
|
343
|
+
for (const wsType of bootstrapConfig.threadTypesAfterOnboarding) {
|
|
344
|
+
if (threadThreadsByType.has(wsType)) continue
|
|
345
|
+
const profile = getCoreThreadProfile(wsType)
|
|
346
|
+
yield* getOrCreateThreadEffect(orgId, userId, wsType, {
|
|
347
|
+
members: [...profile.members],
|
|
348
|
+
title: profile.config.title,
|
|
349
|
+
})
|
|
350
|
+
yield* failIfAborted()
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!onboardingCompleted && bootstrapConfig.onboardingWelcome) {
|
|
355
|
+
const onboardingWelcome = bootstrapConfig.onboardingWelcome
|
|
356
|
+
const ownerThread = defaultThreadsByAgent.get(onboardingWelcome.defaultAgentId)
|
|
357
|
+
if (ownerThread?.id) {
|
|
358
|
+
yield* failIfAborted()
|
|
359
|
+
const ownerThreadRef = ensureRecordId(ownerThread.id, TABLES.THREAD)
|
|
360
|
+
yield* deps.threadMessageService
|
|
361
|
+
.ensureBootstrapWelcomeMessageEffect({
|
|
362
|
+
threadId: ownerThreadRef,
|
|
363
|
+
agentId: onboardingWelcome.defaultAgentId,
|
|
364
|
+
text: onboardingWelcome.buildMessageText({ userName: options?.userName }),
|
|
365
|
+
})
|
|
366
|
+
.pipe(
|
|
367
|
+
Effect.mapError(
|
|
368
|
+
(error) =>
|
|
369
|
+
new DatabaseError({ message: 'Failed to write onboarding welcome message.', cause: error }),
|
|
370
|
+
),
|
|
371
|
+
)
|
|
372
|
+
yield* failIfAborted()
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}),
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
getOrCreateDefault: getOrCreateDefaultEffect,
|
|
381
|
+
getOrCreateThread: getOrCreateThreadEffect,
|
|
382
|
+
createThread: createThreadEffect,
|
|
383
|
+
ensureBootstrapThreads: ensureBootstrapThreadsEffect,
|
|
384
|
+
}
|
|
385
|
+
}
|