@lota-sdk/core 0.4.7 → 0.4.9
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 +94 -22
- package/src/ai-gateway/ai-gateway.ts +738 -223
- package/src/config/agent-defaults.ts +176 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/constants.ts +8 -2
- package/src/config/logger.ts +286 -19
- package/src/config/model-constants.ts +1 -0
- package/src/config/thread-defaults.ts +33 -21
- package/src/create-runtime.ts +725 -383
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +856 -598
- package/src/db/memory.ts +398 -275
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +255 -0
- package/src/db/service.ts +726 -761
- package/src/db/startup.ts +140 -66
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +87 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +98 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/layers.ts +228 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +31 -0
- package/src/effect/services.ts +57 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +122 -71
- package/src/index.ts +46 -1
- package/src/openrouter/direct-provider.ts +29 -0
- package/src/queues/autonomous-job.queue.ts +130 -74
- package/src/queues/context-compaction.queue.ts +60 -15
- package/src/queues/delayed-node-promotion.queue.ts +52 -15
- 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 +13 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
- package/src/queues/plan-scheduler.queue.ts +107 -31
- package/src/queues/post-chat-memory.queue.ts +66 -24
- package/src/queues/queue-factory.ts +142 -52
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +54 -9
- package/src/redis/connection.ts +84 -32
- 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 +10 -0
- package/src/redis/stream-context.ts +84 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +4 -1
- package/src/runtime/agent-stream-helpers.ts +20 -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} +114 -91
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +11 -7
- package/src/runtime/helper-model.ts +135 -48
- package/src/runtime/index.ts +7 -7
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -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} +1 -1
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
- package/src/runtime/plugin-resolution.ts +144 -24
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +197 -130
- package/src/runtime/retrieval-adapters.ts +38 -4
- package/src/runtime/runtime-config.ts +165 -61
- package/src/runtime/runtime-extensions.ts +21 -34
- package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
- package/src/runtime/social-chat/social-chat.ts +594 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -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 +172 -94
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +329 -217
- package/src/services/artifact.service.ts +225 -148
- package/src/services/attachment.service.ts +137 -115
- package/src/services/autonomous-job.service.ts +888 -491
- package/src/services/chat-run-registry.service.ts +11 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +162 -90
- 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 +256 -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 +80 -170
- package/src/services/graph-full-routing.ts +182 -0
- package/src/services/index.ts +18 -20
- package/src/services/institutional-memory.service.ts +220 -123
- package/src/services/learned-skill.service.ts +364 -259
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-org-memory.ts +39 -0
- package/src/services/memory/memory-preseeded.ts +80 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
- package/src/services/memory/memory.service.ts +692 -0
- package/src/services/memory/rerank.service.ts +209 -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 +17 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +144 -51
- package/src/services/ownership-dispatcher.service.ts +415 -264
- 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/plan-approval.service.ts +102 -0
- 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 +175 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +398 -0
- package/src/services/plan/plan-deadline.service.ts +547 -0
- package/src/services/plan/plan-event-delivery.service.ts +261 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +475 -0
- package/src/services/plan/plan-executor-helpers.ts +322 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1654 -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 +644 -0
- package/src/services/plan/plan-scheduler.service.ts +385 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +33 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +125 -0
- package/src/services/plugin-executor.service.ts +97 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +296 -230
- package/src/services/recent-activity-title.service.ts +65 -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 +176 -125
- package/src/services/system-executor.service.ts +91 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +369 -0
- package/src/services/thread/thread-listing.ts +198 -0
- package/src/services/thread/thread-memory-block.ts +117 -0
- package/src/services/thread/thread-message.service.ts +363 -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 +1146 -0
- package/src/services/thread/thread-turn-streaming.ts +402 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +343 -0
- package/src/services/thread/thread.service.ts +335 -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 +331 -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 +2 -2
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/memory-reranker.agent.ts +2 -2
- package/src/system-agents/memory.agent.ts +2 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
- package/src/system-agents/skill-extractor.agent.ts +2 -2
- package/src/system-agents/skill-manager.agent.ts +2 -2
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +220 -161
- package/src/tools/fetch-webpage.tool.ts +21 -17
- package/src/tools/firecrawl-client.ts +16 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +49 -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 +26 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +124 -83
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +17 -23
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +95 -16
- 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 +186 -51
- 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 +175 -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 +56 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/context-compaction-runtime.ts +0 -87
- 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 -844
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-approval.service.ts +0 -83
- 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/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
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { Context, Effect, FiberMap, Layer } from 'effect'
|
|
2
|
+
|
|
1
3
|
import { ChatRunRegistry } from '../runtime/chat-run-registry'
|
|
2
4
|
|
|
3
|
-
export
|
|
5
|
+
export class ChatRunRegistryTag extends Context.Service<ChatRunRegistryTag, ChatRunRegistry>()('ChatRunRegistry') {}
|
|
6
|
+
|
|
7
|
+
export const ChatRunRegistryLive = Layer.effect(
|
|
8
|
+
ChatRunRegistryTag,
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const trackedRuns = yield* FiberMap.make<string>()
|
|
11
|
+
return new ChatRunRegistry(trackedRuns)
|
|
12
|
+
}),
|
|
13
|
+
)
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
import { Context, Effect, Layer } from 'effect'
|
|
2
3
|
|
|
3
4
|
import { chatLogger } from '../config/logger'
|
|
4
5
|
import type { RecordIdRef } from '../db/record-id'
|
|
5
6
|
import { recordIdToString } from '../db/record-id'
|
|
6
|
-
import {
|
|
7
|
+
import type { SurrealDBService } from '../db/service'
|
|
7
8
|
import { TABLES } from '../db/tables'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
9
|
+
import { BadRequestError } from '../effect/errors'
|
|
10
|
+
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
11
|
+
import type { RedisConnectionManager } from '../redis/connection'
|
|
12
|
+
import { withLeaseLock } from '../redis/redis-lease-lock'
|
|
13
|
+
import {
|
|
14
|
+
CONTEXT_WINDOW_TOKENS,
|
|
15
|
+
THREAD_RAW_TAIL_MESSAGES,
|
|
16
|
+
} from '../runtime/context-compaction/context-compaction-constants'
|
|
17
|
+
import { createWiredContextCompactionRuntime } from '../runtime/context-compaction/context-compaction-runtime'
|
|
18
|
+
import type { HelperModelRuntime } from '../runtime/helper-model'
|
|
19
|
+
import { HelperModelTag } from '../runtime/helper-model'
|
|
20
|
+
import { nowEpochMillis } from '../utils/date-time'
|
|
21
|
+
import type { makeThreadMessageService } from './thread/thread-message.service'
|
|
22
|
+
import { ThreadMessageServiceTag } from './thread/thread-message.service'
|
|
23
|
+
import { ThreadSchema } from './thread/thread.types'
|
|
14
24
|
|
|
15
25
|
interface PersistedCompactionMetrics {
|
|
16
26
|
domain: 'thread'
|
|
@@ -24,87 +34,127 @@ interface PersistedCompactionMetrics {
|
|
|
24
34
|
estimatedTokens: number
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
estimateThreshold(contextSize = CONTEXT_WINDOW_TOKENS): number {
|
|
33
|
-
return contextCompactionRuntime.estimateThreshold(contextSize)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
shouldCompactHistory(params: { summaryText: string; liveMessages: ChatMessage[]; contextSize?: number }) {
|
|
37
|
-
return contextCompactionRuntime.shouldCompactHistory(params)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async compactThreadHistory(params: { threadId: RecordIdRef; contextSize?: number }): Promise<{ compacted: boolean }> {
|
|
41
|
-
const entityId = recordIdToString(params.threadId, TABLES.THREAD)
|
|
42
|
-
|
|
43
|
-
return withRedisLeaseLock(
|
|
44
|
-
{
|
|
45
|
-
redis: getRedisConnection(),
|
|
46
|
-
lockKey: `compaction:lock:${entityId}`,
|
|
47
|
-
lockTtlMs: 120_000,
|
|
48
|
-
maxWaitMs: 30_000,
|
|
49
|
-
label: 'context-compaction',
|
|
50
|
-
},
|
|
51
|
-
async () => {
|
|
52
|
-
const thread = await databaseService.findOne(TABLES.THREAD, { id: params.threadId }, ThreadSchema)
|
|
53
|
-
if (!thread) {
|
|
54
|
-
throw new Error(`Thread not found for compaction: ${entityId}`)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const liveMessages = await threadMessageService.listMessagesAfterCursor(
|
|
58
|
-
params.threadId,
|
|
59
|
-
typeof thread.lastCompactedMessageId === 'string' ? thread.lastCompactedMessageId : undefined,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
const result = await contextCompactionRuntime.compactHistory({
|
|
63
|
-
summaryText: typeof thread.compactionSummary === 'string' ? thread.compactionSummary : '',
|
|
64
|
-
liveMessages,
|
|
65
|
-
tailMessageCount: THREAD_RAW_TAIL_MESSAGES,
|
|
66
|
-
contextSize: params.contextSize,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
if (!result.compacted || !result.lastCompactedMessageId) {
|
|
70
|
-
return { compacted: false }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (result.compactedMessages.length > 0) {
|
|
74
|
-
await threadMessageService.upsertMessages({ threadId: params.threadId, messages: result.compactedMessages })
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await databaseService.update(
|
|
78
|
-
TABLES.THREAD,
|
|
79
|
-
params.threadId,
|
|
80
|
-
{ compactionSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId },
|
|
81
|
-
ThreadSchema,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
this.logCompactionMetrics({
|
|
85
|
-
domain: 'thread',
|
|
86
|
-
entityId,
|
|
87
|
-
inputChars: result.inputChars,
|
|
88
|
-
outputChars: result.outputChars,
|
|
89
|
-
savedChars: Math.max(0, result.inputChars - result.outputChars),
|
|
90
|
-
summaryLength: result.summaryText.length,
|
|
91
|
-
compactedMessageCount: result.compactedMessageCount,
|
|
92
|
-
remainingMessageCount: result.remainingMessageCount,
|
|
93
|
-
estimatedTokens: result.estimatedTokens,
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
return { compacted: true }
|
|
97
|
-
},
|
|
98
|
-
)
|
|
99
|
-
}
|
|
37
|
+
function logCompactionMetrics(metrics: PersistedCompactionMetrics): void {
|
|
38
|
+
chatLogger.info`Persisted chat compaction applied metrics=${JSON.stringify(metrics)}`
|
|
39
|
+
}
|
|
100
40
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
41
|
+
interface ContextCompactionDeps {
|
|
42
|
+
db: SurrealDBService
|
|
43
|
+
redis: RedisConnectionManager
|
|
44
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
45
|
+
helperModelRuntime: HelperModelRuntime
|
|
46
|
+
}
|
|
104
47
|
|
|
105
|
-
|
|
106
|
-
|
|
48
|
+
export function makeContextCompactionService(deps: ContextCompactionDeps) {
|
|
49
|
+
const { db, redis, threadMessageService, helperModelRuntime } = deps
|
|
50
|
+
const contextCompactionRuntime = createWiredContextCompactionRuntime({
|
|
51
|
+
helperModelRuntime,
|
|
52
|
+
now: nowEpochMillis,
|
|
53
|
+
randomId: () => Bun.randomUUIDv7(),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const compactThreadHistoryEffect = (params: { threadId: RecordIdRef; contextSize?: number }) =>
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const entityId = recordIdToString(params.threadId, TABLES.THREAD)
|
|
59
|
+
|
|
60
|
+
return yield* withLeaseLock(
|
|
61
|
+
{
|
|
62
|
+
redis: redis.getConnection(),
|
|
63
|
+
lockKey: `compaction:lock:${entityId}`,
|
|
64
|
+
lockTtlMs: 120_000,
|
|
65
|
+
maxWaitMs: 30_000,
|
|
66
|
+
label: 'context-compaction',
|
|
67
|
+
},
|
|
68
|
+
() =>
|
|
69
|
+
Effect.gen(function* () {
|
|
70
|
+
const thread = yield* db.findOne(TABLES.THREAD, { id: params.threadId }, ThreadSchema)
|
|
71
|
+
if (!thread) {
|
|
72
|
+
return yield* new BadRequestError({ message: `Thread not found for compaction: ${entityId}` })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const liveMessages = yield* threadMessageService.listMessagesAfterCursorEffect(
|
|
76
|
+
params.threadId,
|
|
77
|
+
typeof thread.lastCompactedMessageId === 'string' ? thread.lastCompactedMessageId : undefined,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const result = yield* Effect.tryPromise(() =>
|
|
81
|
+
contextCompactionRuntime.compactHistory({
|
|
82
|
+
summaryText: typeof thread.compactionSummary === 'string' ? thread.compactionSummary : '',
|
|
83
|
+
liveMessages,
|
|
84
|
+
tailMessageCount: THREAD_RAW_TAIL_MESSAGES,
|
|
85
|
+
contextSize: params.contextSize,
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if (!result.compacted || !result.lastCompactedMessageId) {
|
|
90
|
+
return { compacted: false }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result.compactedMessages.length > 0) {
|
|
94
|
+
yield* threadMessageService.upsertMessagesEffect({
|
|
95
|
+
threadId: params.threadId,
|
|
96
|
+
messages: result.compactedMessages,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
yield* db.update(
|
|
101
|
+
TABLES.THREAD,
|
|
102
|
+
params.threadId,
|
|
103
|
+
{ compactionSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId },
|
|
104
|
+
ThreadSchema,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
logCompactionMetrics({
|
|
108
|
+
domain: 'thread',
|
|
109
|
+
entityId,
|
|
110
|
+
inputChars: result.inputChars,
|
|
111
|
+
outputChars: result.outputChars,
|
|
112
|
+
savedChars: Math.max(0, result.inputChars - result.outputChars),
|
|
113
|
+
summaryLength: result.summaryText.length,
|
|
114
|
+
compactedMessageCount: result.compactedMessageCount,
|
|
115
|
+
remainingMessageCount: result.remainingMessageCount,
|
|
116
|
+
estimatedTokens: result.estimatedTokens,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return { compacted: true }
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const compactMemoryBlockEffect = (params: { previousSummary: string; newEntriesText: string }) =>
|
|
125
|
+
Effect.tryPromise(() => contextCompactionRuntime.compactMemoryBlockSummary(params))
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
createSummaryMessage(summaryText: string) {
|
|
129
|
+
return contextCompactionRuntime.createSummaryMessage(summaryText)
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
estimateThreshold(contextSize = CONTEXT_WINDOW_TOKENS): number {
|
|
133
|
+
return contextCompactionRuntime.estimateThreshold(contextSize)
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
shouldCompactHistory(params: { summaryText: string; liveMessages: ChatMessage[]; contextSize?: number }) {
|
|
137
|
+
return contextCompactionRuntime.shouldCompactHistory(params)
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
compactThreadHistory: compactThreadHistoryEffect,
|
|
141
|
+
|
|
142
|
+
compactMemoryBlock: compactMemoryBlockEffect,
|
|
107
143
|
}
|
|
108
144
|
}
|
|
109
145
|
|
|
110
|
-
export
|
|
146
|
+
export class ContextCompactionServiceTag extends Context.Service<
|
|
147
|
+
ContextCompactionServiceTag,
|
|
148
|
+
ReturnType<typeof makeContextCompactionService>
|
|
149
|
+
>()('ContextCompactionService') {}
|
|
150
|
+
|
|
151
|
+
export const ContextCompactionServiceLive = Layer.effect(
|
|
152
|
+
ContextCompactionServiceTag,
|
|
153
|
+
Effect.gen(function* () {
|
|
154
|
+
const db = yield* DatabaseServiceTag
|
|
155
|
+
const redis = yield* RedisServiceTag
|
|
156
|
+
const threadMessageService = yield* ThreadMessageServiceTag
|
|
157
|
+
const helperModelRuntime = yield* HelperModelTag
|
|
158
|
+
return makeContextCompactionService({ db, redis, threadMessageService, helperModelRuntime })
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { Context, Schema, Effect, Layer } from 'effect'
|
|
2
|
+
|
|
1
3
|
import { chunkMarkdownDocument, chunkPagedDocument, chunkPlainTextDocument } from '../document/org-document-chunking'
|
|
2
4
|
import type { ParsedDocumentChunk } from '../document/org-document-chunking'
|
|
3
|
-
import {
|
|
5
|
+
import { RuntimeConfigServiceTag } from '../effect/services'
|
|
6
|
+
import { ProviderEmbeddings } from '../embeddings/provider'
|
|
7
|
+
import { sha256Hex } from '../utils/crypto'
|
|
4
8
|
import { CHARS_PER_TOKEN_ESTIMATE } from '../utils/string'
|
|
5
9
|
|
|
6
10
|
type DocumentChunkEmbeddings = {
|
|
@@ -8,15 +12,19 @@ type DocumentChunkEmbeddings = {
|
|
|
8
12
|
embedQuery(query: string): Promise<number[]>
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
function createDocumentChunkEmbeddings(): DocumentChunkEmbeddings {
|
|
12
|
-
const embeddings =
|
|
15
|
+
function createDocumentChunkEmbeddings(embeddingModel: string, openRouterApiKey?: string): DocumentChunkEmbeddings {
|
|
16
|
+
const embeddings = new ProviderEmbeddings({ modelId: embeddingModel, openRouterApiKey })
|
|
13
17
|
|
|
14
18
|
return {
|
|
15
|
-
embedDocuments:
|
|
16
|
-
embedQuery:
|
|
19
|
+
embedDocuments: (documents) => embeddings.embedDocuments(documents),
|
|
20
|
+
embedQuery: (query) => embeddings.embedQuery(query),
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
function estimateDocumentChunkTokenCount(content: string): number {
|
|
25
|
+
return Math.max(1, Math.ceil(content.length / (CHARS_PER_TOKEN_ESTIMATE + 1)))
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
export interface VersionedDocumentChunkRecordShape {
|
|
21
29
|
chunkKey: string
|
|
22
30
|
chunkIndex: number
|
|
@@ -28,46 +36,20 @@ export interface VersionedDocumentChunkRecordShape {
|
|
|
28
36
|
archivedAt?: string | number | Date | null
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
export
|
|
32
|
-
constructor(private readonly embeddings: DocumentChunkEmbeddings = createDocumentChunkEmbeddings()) {}
|
|
33
|
-
|
|
39
|
+
export interface DocumentChunkService {
|
|
34
40
|
buildChunks(params: {
|
|
35
41
|
source: string
|
|
36
42
|
renderMode: 'markdown' | 'text' | 'pdf'
|
|
37
43
|
text: string
|
|
38
44
|
sectionPath?: string
|
|
39
45
|
pages?: Array<{ pageNumber: number; text: string }>
|
|
40
|
-
}): ParsedDocumentChunk[]
|
|
41
|
-
|
|
42
|
-
params.source === 'indexedOutcome' ||
|
|
43
|
-
params.source === 'websiteIntelligence' ||
|
|
44
|
-
params.renderMode === 'markdown'
|
|
45
|
-
) {
|
|
46
|
-
return chunkMarkdownDocument({ text: params.text, baseSectionPath: params.sectionPath })
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (params.renderMode === 'pdf' && params.pages && params.pages.length > 0) {
|
|
50
|
-
return chunkPagedDocument({ pages: params.pages })
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return chunkPlainTextDocument({ text: params.text, sectionPath: params.sectionPath })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
hashContent(content: string): string {
|
|
57
|
-
return new Bun.CryptoHasher('sha256').update(content).digest('hex')
|
|
58
|
-
}
|
|
59
|
-
|
|
46
|
+
}): ParsedDocumentChunk[]
|
|
47
|
+
hashContent(content: string): string
|
|
60
48
|
// Uses 4 chars/token (conservative estimate for document content which tends
|
|
61
49
|
// to have longer words than conversational text where 3 chars/token is used).
|
|
62
|
-
estimateTokenCount(content: string): number
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
async embedQuery(query: string): Promise<number[]> {
|
|
67
|
-
return this.embeddings.embedQuery(query)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async syncVersionedChunks<TRecord, TPayload>(params: {
|
|
50
|
+
estimateTokenCount(content: string): number
|
|
51
|
+
embedQuery(query: string): Promise<number[]>
|
|
52
|
+
syncVersionedChunks<TRecord, TPayload>(params: {
|
|
71
53
|
sourceVersionKey: string
|
|
72
54
|
chunks: ParsedDocumentChunk[]
|
|
73
55
|
loadExisting: () => Promise<TRecord[]>
|
|
@@ -81,64 +63,154 @@ export class DocumentChunkService {
|
|
|
81
63
|
tokenEstimate: number
|
|
82
64
|
}) => TPayload
|
|
83
65
|
selectShape: (row: TRecord) => VersionedDocumentChunkRecordShape
|
|
84
|
-
}):
|
|
85
|
-
|
|
86
|
-
const existingByChunkKey = new Map(
|
|
87
|
-
existingRows
|
|
88
|
-
.filter((row) => params.selectShape(row).sourceVersionKey === params.sourceVersionKey)
|
|
89
|
-
.map((row) => [params.selectShape(row).chunkKey, row]),
|
|
90
|
-
)
|
|
91
|
-
const embeddings = await this.embeddings.embedDocuments(params.chunks.map((chunk) => chunk.content))
|
|
92
|
-
const seenChunkKeys = new Set<string>()
|
|
93
|
-
const staleVersionRows = existingRows.filter(
|
|
94
|
-
(row) => params.selectShape(row).sourceVersionKey !== params.sourceVersionKey,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
await params.archive(staleVersionRows)
|
|
98
|
-
|
|
99
|
-
await Promise.all(
|
|
100
|
-
params.chunks.map(async (chunk, index) => {
|
|
101
|
-
const contentHash = this.hashContent(chunk.content)
|
|
102
|
-
const existingRow = existingByChunkKey.get(chunk.chunkKey)
|
|
103
|
-
const payload = params.buildPayload({
|
|
104
|
-
chunk,
|
|
105
|
-
embedding: embeddings[index] ?? [],
|
|
106
|
-
contentHash,
|
|
107
|
-
tokenEstimate: this.estimateTokenCount(chunk.content),
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
seenChunkKeys.add(chunk.chunkKey)
|
|
111
|
-
|
|
112
|
-
if (!existingRow) {
|
|
113
|
-
await params.create(payload)
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const current = params.selectShape(existingRow)
|
|
118
|
-
const hasChanged =
|
|
119
|
-
current.contentHash !== contentHash ||
|
|
120
|
-
current.chunkIndex !== chunk.chunkIndex ||
|
|
121
|
-
(current.sectionPath ?? null) !== (chunk.sectionPath ?? null) ||
|
|
122
|
-
(current.pageStart ?? null) !== (chunk.pageStart ?? null) ||
|
|
123
|
-
(current.pageEnd ?? null) !== (chunk.pageEnd ?? null)
|
|
124
|
-
|
|
125
|
-
if (!hasChanged) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
66
|
+
}): Effect.Effect<void, DocumentChunkServiceError>
|
|
67
|
+
}
|
|
128
68
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
69
|
+
class DocumentChunkServiceError extends Schema.TaggedErrorClass<DocumentChunkServiceError>()(
|
|
70
|
+
'DocumentChunkServiceError',
|
|
71
|
+
{ message: Schema.String, cause: Schema.Defect },
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
function tryDocumentChunkPromise<A>(
|
|
75
|
+
message: string,
|
|
76
|
+
thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
77
|
+
): Effect.Effect<A, DocumentChunkServiceError> {
|
|
78
|
+
return Effect.suspend(() => {
|
|
79
|
+
try {
|
|
80
|
+
const value = thunk()
|
|
81
|
+
if (Effect.isEffect(value)) {
|
|
82
|
+
return value.pipe(Effect.mapError((cause) => new DocumentChunkServiceError({ message, cause })))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Effect.tryPromise({
|
|
86
|
+
try: () => Promise.resolve(value),
|
|
87
|
+
catch: (cause) => new DocumentChunkServiceError({ message, cause }),
|
|
88
|
+
})
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
return Effect.fail(new DocumentChunkServiceError({ message, cause }))
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
132
94
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
95
|
+
export function makeDocumentChunkService(embeddings: DocumentChunkEmbeddings): DocumentChunkService {
|
|
96
|
+
return {
|
|
97
|
+
buildChunks(params) {
|
|
98
|
+
if (
|
|
99
|
+
params.source === 'indexedOutcome' ||
|
|
100
|
+
params.source === 'websiteIntelligence' ||
|
|
101
|
+
params.renderMode === 'markdown'
|
|
102
|
+
) {
|
|
103
|
+
return chunkMarkdownDocument({ text: params.text, baseSectionPath: params.sectionPath })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (params.renderMode === 'pdf' && params.pages && params.pages.length > 0) {
|
|
107
|
+
return chunkPagedDocument({ pages: params.pages })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return chunkPlainTextDocument({ text: params.text, sectionPath: params.sectionPath })
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
hashContent(content) {
|
|
114
|
+
return sha256Hex(content)
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
estimateTokenCount(content) {
|
|
118
|
+
return estimateDocumentChunkTokenCount(content)
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
embedQuery(query) {
|
|
122
|
+
return embeddings.embedQuery(query)
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
syncVersionedChunks(params) {
|
|
126
|
+
return Effect.gen(function* () {
|
|
127
|
+
const [existingRows, embeddedChunks] = yield* Effect.all([
|
|
128
|
+
tryDocumentChunkPromise('Failed to load existing document chunks.', () => params.loadExisting()),
|
|
129
|
+
tryDocumentChunkPromise('Failed to embed document chunks.', () =>
|
|
130
|
+
embeddings.embedDocuments(params.chunks.map((chunk) => chunk.content)),
|
|
131
|
+
),
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
const existingByChunkKey = new Map(
|
|
135
|
+
existingRows
|
|
136
|
+
.filter((row) => params.selectShape(row).sourceVersionKey === params.sourceVersionKey)
|
|
137
|
+
.map((row) => [params.selectShape(row).chunkKey, row]),
|
|
138
|
+
)
|
|
139
|
+
const seenChunkKeys = new Set<string>()
|
|
140
|
+
const staleVersionRows = existingRows.filter(
|
|
141
|
+
(row) => params.selectShape(row).sourceVersionKey !== params.sourceVersionKey,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
yield* tryDocumentChunkPromise('Failed to archive stale document chunks.', () =>
|
|
145
|
+
params.archive(staleVersionRows),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
yield* Effect.forEach(
|
|
149
|
+
params.chunks,
|
|
150
|
+
(chunk, index) =>
|
|
151
|
+
Effect.gen(function* () {
|
|
152
|
+
const contentHash = sha256Hex(chunk.content)
|
|
153
|
+
const existingRow = existingByChunkKey.get(chunk.chunkKey)
|
|
154
|
+
const payload = params.buildPayload({
|
|
155
|
+
chunk,
|
|
156
|
+
embedding: embeddedChunks[index] ?? [],
|
|
157
|
+
contentHash,
|
|
158
|
+
tokenEstimate: estimateDocumentChunkTokenCount(chunk.content),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
seenChunkKeys.add(chunk.chunkKey)
|
|
162
|
+
|
|
163
|
+
if (!existingRow) {
|
|
164
|
+
yield* tryDocumentChunkPromise(`Failed to create document chunk ${chunk.chunkKey}.`, () =>
|
|
165
|
+
params.create(payload),
|
|
166
|
+
)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const current = params.selectShape(existingRow)
|
|
171
|
+
const hasChanged =
|
|
172
|
+
current.contentHash !== contentHash ||
|
|
173
|
+
current.chunkIndex !== chunk.chunkIndex ||
|
|
174
|
+
(current.sectionPath ?? null) !== (chunk.sectionPath ?? null) ||
|
|
175
|
+
(current.pageStart ?? null) !== (chunk.pageStart ?? null) ||
|
|
176
|
+
(current.pageEnd ?? null) !== (chunk.pageEnd ?? null)
|
|
177
|
+
|
|
178
|
+
if (!hasChanged) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
yield* tryDocumentChunkPromise(`Failed to update document chunk ${chunk.chunkKey}.`, () =>
|
|
183
|
+
params.update(existingRow, payload),
|
|
184
|
+
)
|
|
185
|
+
}),
|
|
186
|
+
{ concurrency: 'unbounded', discard: true },
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const removedCurrentVersionRows = existingRows.filter((row) => {
|
|
190
|
+
const current = params.selectShape(row)
|
|
191
|
+
return current.sourceVersionKey === params.sourceVersionKey && !seenChunkKeys.has(current.chunkKey)
|
|
192
|
+
})
|
|
137
193
|
|
|
138
|
-
|
|
194
|
+
yield* tryDocumentChunkPromise('Failed to archive removed current-version chunks.', () =>
|
|
195
|
+
params.archive(removedCurrentVersionRows),
|
|
196
|
+
)
|
|
197
|
+
})
|
|
198
|
+
},
|
|
139
199
|
}
|
|
140
200
|
}
|
|
141
201
|
|
|
142
|
-
export
|
|
202
|
+
export class DocumentChunkServiceTag extends Context.Service<DocumentChunkServiceTag, DocumentChunkService>()(
|
|
203
|
+
'DocumentChunkService',
|
|
204
|
+
) {}
|
|
205
|
+
|
|
206
|
+
export const DocumentChunkServiceLive = Layer.effect(
|
|
207
|
+
DocumentChunkServiceTag,
|
|
208
|
+
Effect.gen(function* () {
|
|
209
|
+
const runtimeConfig = yield* RuntimeConfigServiceTag
|
|
210
|
+
return makeDocumentChunkService(
|
|
211
|
+
createDocumentChunkEmbeddings(runtimeConfig.aiGateway.embeddingModel, runtimeConfig.aiGateway.openRouterApiKey),
|
|
212
|
+
)
|
|
213
|
+
}),
|
|
214
|
+
)
|
|
143
215
|
|
|
144
216
|
export type { ParsedDocumentChunk }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { readApprovalContinuationResponse } from '../../runtime/approval-continuation'
|
|
4
|
+
import type { HumanNodeResponsePayload } from '../plan/plan-executor-helpers'
|
|
5
|
+
|
|
6
|
+
export function buildApprovalResponseFromMessages(
|
|
7
|
+
messages: ChatMessage[],
|
|
8
|
+
): { approvalId: string; response: HumanNodeResponsePayload } | null {
|
|
9
|
+
for (const message of [...messages].reverse()) {
|
|
10
|
+
if (message.role !== 'assistant') continue
|
|
11
|
+
const approvalResponse = readApprovalContinuationResponse(message)
|
|
12
|
+
if (!approvalResponse) continue
|
|
13
|
+
|
|
14
|
+
const response: HumanNodeResponsePayload = {
|
|
15
|
+
approved: approvalResponse.approved,
|
|
16
|
+
requiredEdits: [...approvalResponse.requiredEdits],
|
|
17
|
+
}
|
|
18
|
+
if (approvalResponse.comments) {
|
|
19
|
+
response.comments = approvalResponse.comments
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { approvalId: approvalResponse.approvalId, response }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RecordIdInput } from '../../db/record-id'
|
|
2
|
+
import { recordIdToString } from '../../db/record-id'
|
|
3
|
+
import { TABLES } from '../../db/tables'
|
|
4
|
+
|
|
5
|
+
export function aggregateBlockingIssues(issues: Array<{ code: string; message: string }>): string {
|
|
6
|
+
return issues.map((issue) => `${issue.code}: ${issue.message}`).join(' | ')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function hasCrossThreadSourceContext(
|
|
10
|
+
sourceThreadId: RecordIdInput | undefined,
|
|
11
|
+
threadId: RecordIdInput,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (!sourceThreadId) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return recordIdToString(sourceThreadId, TABLES.THREAD) !== recordIdToString(threadId, TABLES.THREAD)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isPlanVisibleInThreadContext(
|
|
21
|
+
threadId: RecordIdInput,
|
|
22
|
+
params: { threadId: RecordIdInput; sourceThreadId?: RecordIdInput },
|
|
23
|
+
): boolean {
|
|
24
|
+
const currentThreadId = recordIdToString(threadId, TABLES.THREAD)
|
|
25
|
+
return (
|
|
26
|
+
currentThreadId === recordIdToString(params.threadId, TABLES.THREAD) ||
|
|
27
|
+
(params.sourceThreadId !== undefined && currentThreadId === recordIdToString(params.sourceThreadId, TABLES.THREAD))
|
|
28
|
+
)
|
|
29
|
+
}
|