@lota-sdk/core 0.1.14 → 0.1.16
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/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +12 -5
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +386 -58
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +87 -20
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +5 -2
- package/src/system-agents/index.ts +8 -0
- 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 +1 -1
- package/src/tools/execution-plan.tool.ts +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
package/src/workers/bootstrap.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { configureLotaLogger, serverLogger } from '../config/logger'
|
|
2
2
|
import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
|
|
3
3
|
import { SurrealDBService, databaseService, setDatabaseService } from '../db/service'
|
|
4
4
|
import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
|
|
@@ -27,7 +27,7 @@ export async function initializeSandboxedWorkerRuntime(): Promise<void> {
|
|
|
27
27
|
|
|
28
28
|
sandboxedWorkerRuntimePromise = (async () => {
|
|
29
29
|
const env = parseWorkerBootstrapEnv(process.env)
|
|
30
|
-
await
|
|
30
|
+
await configureLotaLogger()
|
|
31
31
|
|
|
32
32
|
ensureDatabaseServiceConfigured()
|
|
33
33
|
|
package/src/workers/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SandboxedJob } from 'bullmq'
|
|
2
2
|
import { BoundQuery, eq, inside } from 'surrealdb'
|
|
3
3
|
|
|
4
|
+
import { MEMORY } from '../config/constants'
|
|
4
5
|
import { serverLogger } from '../config/logger'
|
|
5
6
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
6
7
|
import type { RecordIdInput } from '../db/record-id'
|
|
@@ -16,6 +17,7 @@ await initializeSandboxedWorkerRuntime()
|
|
|
16
17
|
const MEMORY_TABLE = TABLES.MEMORY
|
|
17
18
|
const MEMORY_RELATION_TABLE = TABLES.MEMORY_RELATION
|
|
18
19
|
const MEMORY_HISTORY_TABLE = TABLES.MEMORY_HISTORY
|
|
20
|
+
const RELATION_SUPERSEDES = 'supersedes' as const
|
|
19
21
|
const HARD_SIMILARITY_THRESHOLD = 0.95
|
|
20
22
|
const SOFT_SIMILARITY_THRESHOLD = 0.9
|
|
21
23
|
const MAX_MEMORIES_PER_SCOPE = 500
|
|
@@ -130,7 +132,7 @@ async function deduplicateScope(scopeId: string): Promise<number> {
|
|
|
130
132
|
ensureRecordId(winner.id, TABLES.MEMORY),
|
|
131
133
|
MEMORY_RELATION_TABLE,
|
|
132
134
|
ensureRecordId(loser.id, TABLES.MEMORY),
|
|
133
|
-
{ relationType:
|
|
135
|
+
{ relationType: RELATION_SUPERSEDES, confidence: 1.0 },
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
await databaseService.query(
|
|
@@ -187,13 +189,13 @@ async function collapseSupersedeCh(): Promise<number> {
|
|
|
187
189
|
new BoundQuery(
|
|
188
190
|
`SELECT
|
|
189
191
|
id AS middleId,
|
|
190
|
-
<-${MEMORY_RELATION_TABLE}[WHERE relationType = '
|
|
191
|
-
->${MEMORY_RELATION_TABLE}[WHERE relationType = '
|
|
192
|
+
<-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']<-${MEMORY_TABLE}.id AS predecessors,
|
|
193
|
+
->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']->${MEMORY_TABLE}.id AS successors
|
|
192
194
|
FROM ${MEMORY_TABLE}
|
|
193
195
|
WHERE archivedAt IS NONE
|
|
194
|
-
AND count(->${MEMORY_RELATION_TABLE}[WHERE relationType = '
|
|
195
|
-
AND count(<-${MEMORY_RELATION_TABLE}[WHERE relationType = '
|
|
196
|
-
LIMIT
|
|
196
|
+
AND count(->${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
|
|
197
|
+
AND count(<-${MEMORY_RELATION_TABLE}[WHERE relationType = '${RELATION_SUPERSEDES}']) > 0
|
|
198
|
+
LIMIT ${MEMORY.MAX_KNN_LIMIT}`,
|
|
197
199
|
),
|
|
198
200
|
)
|
|
199
201
|
|
|
@@ -210,7 +212,7 @@ async function collapseSupersedeCh(): Promise<number> {
|
|
|
210
212
|
const existing = await databaseService.query<{ id: RecordIdInput }>(
|
|
211
213
|
new BoundQuery(
|
|
212
214
|
`SELECT id FROM ${MEMORY_RELATION_TABLE}
|
|
213
|
-
WHERE in = $predId AND out = $succId AND relationType = '
|
|
215
|
+
WHERE in = $predId AND out = $succId AND relationType = '${RELATION_SUPERSEDES}'
|
|
214
216
|
LIMIT 1`,
|
|
215
217
|
{ predId: predRef, succId: succRef },
|
|
216
218
|
),
|
|
@@ -218,7 +220,7 @@ async function collapseSupersedeCh(): Promise<number> {
|
|
|
218
220
|
|
|
219
221
|
if (existing.length === 0) {
|
|
220
222
|
await databaseService.relate(predRef, MEMORY_RELATION_TABLE, succRef, {
|
|
221
|
-
relationType:
|
|
223
|
+
relationType: RELATION_SUPERSEDES,
|
|
222
224
|
confidence: 1.0,
|
|
223
225
|
})
|
|
224
226
|
}
|
|
@@ -288,10 +290,8 @@ const handler = async (job: SandboxedJob<MemoryConsolidationJob>) => {
|
|
|
288
290
|
new BoundQuery(`SELECT VALUE scopeId FROM ${MEMORY_TABLE} WHERE archivedAt IS NONE GROUP BY scopeId`),
|
|
289
291
|
)
|
|
290
292
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
totalMerged += merged
|
|
294
|
-
}
|
|
293
|
+
const results = await Promise.all(scopeIds.map(deduplicateScope))
|
|
294
|
+
totalMerged = results.reduce((a, b) => a + b, 0)
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
const pruned = await pruneStaleMemories()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAgentName } from '../config/agent-defaults'
|
|
2
|
+
import { compactWhitespace } from '../utils/string'
|
|
2
3
|
|
|
3
4
|
interface DigestMessageForTranscript {
|
|
4
5
|
source: 'workstream'
|
|
@@ -8,10 +9,6 @@ interface DigestMessageForTranscript {
|
|
|
8
9
|
metadata?: Record<string, unknown>
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
function normalizeWhitespace(value: string): string {
|
|
12
|
-
return value.replace(/\s+/g, ' ').trim()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
12
|
function normalizeFilePartMetadata(part: Record<string, unknown>): string | null {
|
|
16
13
|
if (part.type !== 'file') return null
|
|
17
14
|
|
|
@@ -57,9 +54,7 @@ export function buildDigestTranscript(params: { messages: DigestMessageForTransc
|
|
|
57
54
|
|
|
58
55
|
const sourcePrefix = `[${message.source}:${message.sourceId}]`
|
|
59
56
|
const textParts = message.parts
|
|
60
|
-
.flatMap((part) =>
|
|
61
|
-
part.type === 'text' && typeof part.text === 'string' ? [normalizeWhitespace(part.text)] : [],
|
|
62
|
-
)
|
|
57
|
+
.flatMap((part) => (part.type === 'text' && typeof part.text === 'string' ? [compactWhitespace(part.text)] : []))
|
|
63
58
|
.filter((value) => value.length > 0)
|
|
64
59
|
const fileParts = message.parts
|
|
65
60
|
.map((part) => normalizeFilePartMetadata(part))
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toTimestamp } from '@lota-sdk/shared'
|
|
2
1
|
import { BoundQuery } from 'surrealdb'
|
|
3
2
|
import { z } from 'zod'
|
|
4
3
|
|
|
@@ -16,26 +15,20 @@ import { createHelperModelRuntime } from '../runtime/helper-model'
|
|
|
16
15
|
import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
|
|
17
16
|
import { memoryService } from '../services/memory.service'
|
|
18
17
|
import { createRegularChatMemoryDigestAgent } from '../system-agents/regular-chat-memory-digest.agent'
|
|
18
|
+
import { compactWhitespace } from '../utils/string'
|
|
19
19
|
import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
|
|
20
|
+
import {
|
|
21
|
+
compareDigestMessageOrder,
|
|
22
|
+
listEligibleWorkstreamMessages,
|
|
23
|
+
listWorkstreamIdsForOrg,
|
|
24
|
+
normalizeBlock,
|
|
25
|
+
} from './utils/workstream-message-query'
|
|
26
|
+
import type { DigestCursor, DigestMessage } from './utils/workstream-message-query'
|
|
20
27
|
|
|
21
28
|
const StructuredProfilePatchSchema = z.record(z.string(), z.unknown()).default({})
|
|
22
29
|
|
|
23
30
|
const REGULAR_CHAT_MEMORY_DIGEST_TIMEOUT_MS = 10 * 60 * 1000
|
|
24
31
|
const WorkspaceMemoryRowSchema = z.object({ content: z.string() })
|
|
25
|
-
const EntityIdRowSchema = z.string().trim().min(1)
|
|
26
|
-
const RecordTimestampSchema = z.union([z.date(), z.string(), z.number()])
|
|
27
|
-
const MessageRoleSchema = z.enum(['system', 'user', 'assistant'])
|
|
28
|
-
const MessagePartSchema = z.record(z.string(), z.unknown())
|
|
29
|
-
const MessageMetadataSchema = z.record(z.string(), z.unknown()).nullish()
|
|
30
|
-
|
|
31
|
-
const WorkstreamDigestMessageRowSchema = z.object({
|
|
32
|
-
id: z.string(),
|
|
33
|
-
workstreamId: z.string(),
|
|
34
|
-
role: MessageRoleSchema,
|
|
35
|
-
parts: z.array(MessagePartSchema).optional(),
|
|
36
|
-
metadata: MessageMetadataSchema,
|
|
37
|
-
createdAt: RecordTimestampSchema,
|
|
38
|
-
})
|
|
39
32
|
|
|
40
33
|
const ExtractedFactSchema = z.object({
|
|
41
34
|
content: z.string().trim().min(1),
|
|
@@ -52,40 +45,18 @@ const RegularChatMemoryDigestOutputSchema = z.object({
|
|
|
52
45
|
|
|
53
46
|
const helperModelRuntime = createHelperModelRuntime()
|
|
54
47
|
|
|
55
|
-
interface DigestCursor {
|
|
56
|
-
createdAt: Date
|
|
57
|
-
id: string
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface DigestMessage {
|
|
61
|
-
source: 'workstream'
|
|
62
|
-
sourceId: string
|
|
63
|
-
role: 'system' | 'user' | 'assistant'
|
|
64
|
-
parts: Array<Record<string, unknown>>
|
|
65
|
-
metadata?: Record<string, unknown>
|
|
66
|
-
cursor: DigestCursor
|
|
67
|
-
}
|
|
68
|
-
|
|
69
48
|
interface RegularChatDigestRunResult {
|
|
70
49
|
skipped: boolean
|
|
71
50
|
processedWorkstreamMessages: number
|
|
72
51
|
followUpScheduled: boolean
|
|
73
52
|
}
|
|
74
53
|
|
|
75
|
-
function normalizeWhitespace(value: string): string {
|
|
76
|
-
return value.replace(/\s+/g, ' ').trim()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function normalizeBlock(value: string): string {
|
|
80
|
-
return value.replaceAll(String.fromCharCode(0), '').replace(/\r/g, '').trim()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
54
|
function buildMemoryContext(memories: Array<{ content: string }>): string {
|
|
84
55
|
if (memories.length === 0) return 'No existing memories.'
|
|
85
56
|
|
|
86
57
|
return memories
|
|
87
58
|
.map((memory, index) => {
|
|
88
|
-
const content =
|
|
59
|
+
const content = compactWhitespace(memory.content)
|
|
89
60
|
if (!content) return ''
|
|
90
61
|
return `${index + 1}. ${content}`
|
|
91
62
|
})
|
|
@@ -117,75 +88,10 @@ function buildPrompt(params: {
|
|
|
117
88
|
].join('\n')
|
|
118
89
|
}
|
|
119
90
|
|
|
120
|
-
function mapWorkstreamDigestRow(row: z.infer<typeof WorkstreamDigestMessageRowSchema>): DigestMessage {
|
|
121
|
-
return {
|
|
122
|
-
source: 'workstream',
|
|
123
|
-
sourceId: row.workstreamId,
|
|
124
|
-
role: row.role,
|
|
125
|
-
parts: row.parts ?? [],
|
|
126
|
-
metadata: row.metadata ?? undefined,
|
|
127
|
-
cursor: { createdAt: new Date(toTimestamp(row.createdAt)), id: row.id },
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function compareDigestMessageOrder(left: DigestMessage, right: DigestMessage): number {
|
|
132
|
-
const timeDiff = left.cursor.createdAt.getTime() - right.cursor.createdAt.getTime()
|
|
133
|
-
if (timeDiff !== 0) return timeDiff
|
|
134
|
-
return left.cursor.id.localeCompare(right.cursor.id)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
91
|
function getLastCursor(messages: DigestMessage[]): DigestCursor | null {
|
|
138
92
|
return messages.length > 0 ? messages[messages.length - 1].cursor : null
|
|
139
93
|
}
|
|
140
94
|
|
|
141
|
-
async function listWorkstreamIdsForOrg(orgRef: RecordIdRef): Promise<RecordIdRef[]> {
|
|
142
|
-
const ids = await databaseService.query<unknown>(
|
|
143
|
-
new BoundQuery(
|
|
144
|
-
`SELECT VALUE type::string(id) FROM ${TABLES.WORKSTREAM}
|
|
145
|
-
WHERE organizationId = $organizationId`,
|
|
146
|
-
{ organizationId: orgRef },
|
|
147
|
-
),
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
return ids.map((value) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.WORKSTREAM))
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function listEligibleWorkstreamMessages(params: {
|
|
154
|
-
workstreamIds: RecordIdRef[]
|
|
155
|
-
cursor: DigestCursor | null
|
|
156
|
-
onboardingCutoff: Date | null
|
|
157
|
-
}): Promise<DigestMessage[]> {
|
|
158
|
-
if (params.workstreamIds.length === 0) return []
|
|
159
|
-
|
|
160
|
-
let query: BoundQuery | null = null
|
|
161
|
-
if (params.cursor) {
|
|
162
|
-
const cursorRowId = ensureRecordId(params.cursor.id, TABLES.WORKSTREAM_MESSAGE)
|
|
163
|
-
query = new BoundQuery(
|
|
164
|
-
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
165
|
-
WHERE workstreamId IN $workstreamIds
|
|
166
|
-
AND (
|
|
167
|
-
createdAt > $cursorCreatedAt
|
|
168
|
-
OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
|
|
169
|
-
)
|
|
170
|
-
ORDER BY createdAt ASC, id ASC`,
|
|
171
|
-
{ workstreamIds: params.workstreamIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
|
|
172
|
-
)
|
|
173
|
-
} else if (params.onboardingCutoff) {
|
|
174
|
-
query = new BoundQuery(
|
|
175
|
-
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
176
|
-
WHERE workstreamId IN $workstreamIds
|
|
177
|
-
AND createdAt > $onboardingCutoff
|
|
178
|
-
ORDER BY createdAt ASC, id ASC`,
|
|
179
|
-
{ workstreamIds: params.workstreamIds, onboardingCutoff: params.onboardingCutoff },
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (!query) return []
|
|
184
|
-
|
|
185
|
-
const rows = await databaseService.query<unknown>(query)
|
|
186
|
-
return rows.map((row) => mapWorkstreamDigestRow(WorkstreamDigestMessageRowSchema.parse(row)))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
95
|
async function hasNewEligibleWorkstreamMessages(params: {
|
|
190
96
|
workstreamIds: RecordIdRef[]
|
|
191
97
|
cursor: DigestCursor | null
|
|
@@ -224,7 +130,7 @@ async function hasNewEligibleWorkstreamMessages(params: {
|
|
|
224
130
|
}
|
|
225
131
|
|
|
226
132
|
async function loadExistingOrganizationMemories(orgId: string): Promise<Array<{ content: string }>> {
|
|
227
|
-
return
|
|
133
|
+
return databaseService.queryMany(
|
|
228
134
|
new BoundQuery(
|
|
229
135
|
`SELECT content, createdAt, id FROM ${TABLES.MEMORY}
|
|
230
136
|
WHERE metadata.orgId = $orgId
|
|
@@ -249,7 +155,7 @@ export async function runRegularChatMemoryDigest(
|
|
|
249
155
|
return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
|
|
250
156
|
}
|
|
251
157
|
|
|
252
|
-
return
|
|
158
|
+
return withConfiguredWorkspaceMemoryLock(orgId, async () => {
|
|
253
159
|
if (
|
|
254
160
|
!workspaceProvider.getBackgroundCursor ||
|
|
255
161
|
!workspaceProvider.setBackgroundCursor ||
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { toTimestamp } from '@lota-sdk/shared'
|
|
2
|
-
import { BoundQuery } from 'surrealdb'
|
|
3
|
-
import { z } from 'zod'
|
|
4
|
-
|
|
5
1
|
import { serverLogger } from '../config/logger'
|
|
6
2
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
7
|
-
import type { RecordIdRef } from '../db/record-id'
|
|
8
|
-
import { databaseService } from '../db/service'
|
|
9
3
|
import { TABLES } from '../db/tables'
|
|
10
4
|
import { getDefaultEmbeddings } from '../embeddings/provider'
|
|
11
5
|
import type { SkillExtractionJob } from '../queues/skill-extraction.queue'
|
|
@@ -16,38 +10,15 @@ import { createSkillExtractorAgent, SkillExtractionOutputSchema } from '../syste
|
|
|
16
10
|
import type { SkillCandidate } from '../system-agents/skill-extractor.agent'
|
|
17
11
|
import { createSkillManagerAgent, SkillManagerOutputSchema } from '../system-agents/skill-manager.agent'
|
|
18
12
|
import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
|
|
13
|
+
import {
|
|
14
|
+
compareDigestMessageOrder,
|
|
15
|
+
listEligibleWorkstreamMessages,
|
|
16
|
+
listWorkstreamIdsForOrg,
|
|
17
|
+
} from './utils/workstream-message-query'
|
|
19
18
|
|
|
20
19
|
const SKILL_EXTRACTION_TIMEOUT_MS = 10 * 60 * 1000
|
|
21
20
|
const MIN_MESSAGE_THRESHOLD = 10
|
|
22
21
|
|
|
23
|
-
const RecordTimestampSchema = z.union([z.date(), z.string(), z.number()])
|
|
24
|
-
const MessageRoleSchema = z.enum(['system', 'user', 'assistant'])
|
|
25
|
-
const MessagePartSchema = z.record(z.string(), z.unknown())
|
|
26
|
-
const MessageMetadataSchema = z.record(z.string(), z.unknown()).nullish()
|
|
27
|
-
|
|
28
|
-
const WorkstreamMessageRowSchema = z.object({
|
|
29
|
-
id: z.string(),
|
|
30
|
-
workstreamId: z.string(),
|
|
31
|
-
role: MessageRoleSchema,
|
|
32
|
-
parts: z.array(MessagePartSchema).optional(),
|
|
33
|
-
metadata: MessageMetadataSchema,
|
|
34
|
-
createdAt: RecordTimestampSchema,
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
interface DigestCursor {
|
|
38
|
-
createdAt: Date
|
|
39
|
-
id: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface DigestMessage {
|
|
43
|
-
source: 'workstream'
|
|
44
|
-
sourceId: string
|
|
45
|
-
role: 'system' | 'user' | 'assistant'
|
|
46
|
-
parts: Array<Record<string, unknown>>
|
|
47
|
-
metadata?: Record<string, unknown>
|
|
48
|
-
cursor: DigestCursor
|
|
49
|
-
}
|
|
50
|
-
|
|
51
22
|
interface SkillExtractionRunResult {
|
|
52
23
|
skipped: boolean
|
|
53
24
|
processedMessages: number
|
|
@@ -58,71 +29,6 @@ const embeddings = getDefaultEmbeddings()
|
|
|
58
29
|
|
|
59
30
|
const helperModelRuntime = createHelperModelRuntime()
|
|
60
31
|
|
|
61
|
-
function mapWorkstreamRow(row: z.infer<typeof WorkstreamMessageRowSchema>): DigestMessage {
|
|
62
|
-
return {
|
|
63
|
-
source: 'workstream',
|
|
64
|
-
sourceId: row.workstreamId,
|
|
65
|
-
role: row.role,
|
|
66
|
-
parts: row.parts ?? [],
|
|
67
|
-
metadata: row.metadata ?? undefined,
|
|
68
|
-
cursor: { createdAt: new Date(toTimestamp(row.createdAt)), id: row.id },
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function compareMessageOrder(left: DigestMessage, right: DigestMessage): number {
|
|
73
|
-
const timeDiff = left.cursor.createdAt.getTime() - right.cursor.createdAt.getTime()
|
|
74
|
-
if (timeDiff !== 0) return timeDiff
|
|
75
|
-
return left.cursor.id.localeCompare(right.cursor.id)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function listWorkstreamIdsForOrg(orgRef: RecordIdRef): Promise<RecordIdRef[]> {
|
|
79
|
-
const EntityIdRowSchema = z.string().trim().min(1)
|
|
80
|
-
const ids = await databaseService.query<unknown>(
|
|
81
|
-
new BoundQuery(
|
|
82
|
-
`SELECT VALUE type::string(id) FROM ${TABLES.WORKSTREAM}
|
|
83
|
-
WHERE organizationId = $organizationId`,
|
|
84
|
-
{ organizationId: orgRef },
|
|
85
|
-
),
|
|
86
|
-
)
|
|
87
|
-
return ids.map((value) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.WORKSTREAM))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function listEligibleMessages(params: {
|
|
91
|
-
workstreamIds: RecordIdRef[]
|
|
92
|
-
cursor: DigestCursor | null
|
|
93
|
-
onboardingCutoff: Date | null
|
|
94
|
-
}): Promise<DigestMessage[]> {
|
|
95
|
-
if (params.workstreamIds.length === 0) return []
|
|
96
|
-
|
|
97
|
-
let query: BoundQuery | null = null
|
|
98
|
-
if (params.cursor) {
|
|
99
|
-
const cursorRowId = ensureRecordId(params.cursor.id, TABLES.WORKSTREAM_MESSAGE)
|
|
100
|
-
query = new BoundQuery(
|
|
101
|
-
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
102
|
-
WHERE workstreamId IN $workstreamIds
|
|
103
|
-
AND (
|
|
104
|
-
createdAt > $cursorCreatedAt
|
|
105
|
-
OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
|
|
106
|
-
)
|
|
107
|
-
ORDER BY createdAt ASC, id ASC`,
|
|
108
|
-
{ workstreamIds: params.workstreamIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
|
|
109
|
-
)
|
|
110
|
-
} else if (params.onboardingCutoff) {
|
|
111
|
-
query = new BoundQuery(
|
|
112
|
-
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
113
|
-
WHERE workstreamId IN $workstreamIds
|
|
114
|
-
AND createdAt > $onboardingCutoff
|
|
115
|
-
ORDER BY createdAt ASC, id ASC`,
|
|
116
|
-
{ workstreamIds: params.workstreamIds, onboardingCutoff: params.onboardingCutoff },
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!query) return []
|
|
121
|
-
|
|
122
|
-
const rows = await databaseService.query<unknown>(query)
|
|
123
|
-
return rows.map((row) => mapWorkstreamRow(WorkstreamMessageRowSchema.parse(row)))
|
|
124
|
-
}
|
|
125
|
-
|
|
126
32
|
function buildExtractionPrompt(params: { workspaceName: string; transcript: string; existingSkills: string }): string {
|
|
127
33
|
return [
|
|
128
34
|
`Workspace name: ${params.workspaceName}`,
|
|
@@ -180,7 +86,7 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
|
|
|
180
86
|
return { skipped: true, processedMessages: 0, extractedSkills: 0 }
|
|
181
87
|
}
|
|
182
88
|
|
|
183
|
-
return
|
|
89
|
+
return withConfiguredWorkspaceMemoryLock(orgId, async () => {
|
|
184
90
|
const workspace = await cursorAwareWorkspaceProvider.getWorkspace(orgRef)
|
|
185
91
|
const lifecycleState = await cursorAwareWorkspaceProvider.getLifecycleState?.(workspace)
|
|
186
92
|
if (lifecycleState?.bootstrapActive ?? false) {
|
|
@@ -196,14 +102,14 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
|
|
|
196
102
|
})
|
|
197
103
|
|
|
198
104
|
const workstreamIds = await listWorkstreamIdsForOrg(orgRef)
|
|
199
|
-
const messages = await
|
|
105
|
+
const messages = await listEligibleWorkstreamMessages({ workstreamIds, cursor: existingCursor, onboardingCutoff })
|
|
200
106
|
|
|
201
107
|
if (messages.length < MIN_MESSAGE_THRESHOLD) {
|
|
202
108
|
serverLogger.info`Skipping skill extraction for ${orgId}: only ${messages.length} messages (threshold: ${MIN_MESSAGE_THRESHOLD})`
|
|
203
109
|
return { skipped: true, processedMessages: messages.length, extractedSkills: 0 }
|
|
204
110
|
}
|
|
205
111
|
|
|
206
|
-
const sortedMessages = [...messages].sort(
|
|
112
|
+
const sortedMessages = [...messages].sort(compareDigestMessageOrder)
|
|
207
113
|
const { transcript } = buildDigestTranscript({ messages: sortedMessages })
|
|
208
114
|
|
|
209
115
|
const existingSkills = await learnedSkillService.listForOrg(orgId)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { CHARS_PER_TOKEN_ESTIMATE } from '../../utils/string'
|
|
2
|
+
|
|
1
3
|
export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 250_000
|
|
2
|
-
const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
|
|
4
|
+
export const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
|
|
3
5
|
export const DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS = 10_000
|
|
4
6
|
const SECTION_SEPARATOR_LENGTH = 2
|
|
7
|
+
const MIN_CHUNK_CHARS_FLOOR = 512
|
|
5
8
|
|
|
6
9
|
export interface FileSection {
|
|
7
10
|
kind: 'preamble' | 'file'
|
|
@@ -29,7 +32,7 @@ export interface FileSectionChunkOptions {
|
|
|
29
32
|
|
|
30
33
|
function estimateTokenCountFromChars(text: string): number {
|
|
31
34
|
if (!text) return 0
|
|
32
|
-
return Math.ceil(text.length /
|
|
35
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function normalizeMaxChars(value?: number): number {
|
|
@@ -43,7 +46,7 @@ function normalizeMinChunkChars(value: number | undefined, maxChars: number): nu
|
|
|
43
46
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
44
47
|
return Math.min(DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS, Math.floor(maxChars * 0.35))
|
|
45
48
|
}
|
|
46
|
-
const normalized = Math.max(
|
|
49
|
+
const normalized = Math.max(MIN_CHUNK_CHARS_FLOOR, Math.floor(value))
|
|
47
50
|
return Math.min(normalized, Math.floor(maxChars * 0.6))
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -27,7 +27,7 @@ export function parseRepomixFileSections(repomixOutput: string): FileSection[] {
|
|
|
27
27
|
const nextStart = matches[index + 1]?.index ?? source.length
|
|
28
28
|
const content = source.slice(start, nextStart).trim()
|
|
29
29
|
if (!content) continue
|
|
30
|
-
const filePath =
|
|
30
|
+
const filePath = match[1].trim()
|
|
31
31
|
sections.push({ kind: 'file', content, filePath: filePath || undefined })
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -38,5 +38,5 @@ export async function chunkRepomixFileSections(
|
|
|
38
38
|
repomixOutput: string,
|
|
39
39
|
options: FileSectionChunkOptions = {},
|
|
40
40
|
): Promise<FileSectionChunk[]> {
|
|
41
|
-
return
|
|
41
|
+
return chunkFileSections(parseRepomixFileSections(repomixOutput), options)
|
|
42
42
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export interface SandboxedWorkerError {
|
|
2
|
+
name: string
|
|
3
|
+
message: string
|
|
4
|
+
stack?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function toSandboxedWorkerError(error: unknown, context?: string): SandboxedWorkerError {
|
|
8
|
+
const base =
|
|
9
|
+
error instanceof Error
|
|
10
|
+
? { name: error.name || 'Error', message: error.message, stack: error.stack }
|
|
11
|
+
: { name: 'Error', message: String(error) }
|
|
3
12
|
if (context) base.message = `${context}: ${base.message}`
|
|
4
13
|
return base
|
|
5
14
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { requireTimestamp } from '@lota-sdk/shared'
|
|
2
|
+
import { BoundQuery } from 'surrealdb'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
6
|
+
import type { RecordIdRef } from '../../db/record-id'
|
|
7
|
+
import { databaseService } from '../../db/service'
|
|
8
|
+
import { TABLES } from '../../db/tables'
|
|
9
|
+
import { WorkstreamMessageRowSchema } from '../../db/workstream-message-row'
|
|
10
|
+
import type { WorkstreamMessageRow } from '../../db/workstream-message-row'
|
|
11
|
+
import { normalizeTextBody } from '../../document/parsing'
|
|
12
|
+
|
|
13
|
+
export interface DigestCursor {
|
|
14
|
+
createdAt: Date
|
|
15
|
+
id: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DigestMessage {
|
|
19
|
+
source: 'workstream'
|
|
20
|
+
sourceId: string
|
|
21
|
+
role: 'system' | 'user' | 'assistant'
|
|
22
|
+
parts: Array<Record<string, unknown>>
|
|
23
|
+
metadata?: Record<string, unknown>
|
|
24
|
+
cursor: DigestCursor
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mapWorkstreamRow(row: WorkstreamMessageRow): DigestMessage {
|
|
28
|
+
return {
|
|
29
|
+
source: 'workstream',
|
|
30
|
+
sourceId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
|
|
31
|
+
role: row.role,
|
|
32
|
+
parts: row.parts as Array<Record<string, unknown>>,
|
|
33
|
+
metadata: row.metadata ?? undefined,
|
|
34
|
+
cursor: {
|
|
35
|
+
createdAt: new Date(requireTimestamp(row.createdAt)),
|
|
36
|
+
id: recordIdToString(row.id, TABLES.WORKSTREAM_MESSAGE),
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function compareDigestMessageOrder(left: DigestMessage, right: DigestMessage): number {
|
|
42
|
+
const timeDiff = left.cursor.createdAt.getTime() - right.cursor.createdAt.getTime()
|
|
43
|
+
if (timeDiff !== 0) return timeDiff
|
|
44
|
+
return left.cursor.id.localeCompare(right.cursor.id)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function listWorkstreamIdsForOrg(orgRef: RecordIdRef): Promise<RecordIdRef[]> {
|
|
48
|
+
const EntityIdRowSchema = z.string().trim().min(1)
|
|
49
|
+
const ids = await databaseService.query<unknown>(
|
|
50
|
+
new BoundQuery(
|
|
51
|
+
`SELECT VALUE type::string(id) FROM ${TABLES.WORKSTREAM}
|
|
52
|
+
WHERE organizationId = $organizationId`,
|
|
53
|
+
{ organizationId: orgRef },
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
return ids.map((value) => ensureRecordId(EntityIdRowSchema.parse(value), TABLES.WORKSTREAM))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function listEligibleWorkstreamMessages(params: {
|
|
60
|
+
workstreamIds: RecordIdRef[]
|
|
61
|
+
cursor: DigestCursor | null
|
|
62
|
+
onboardingCutoff: Date | null
|
|
63
|
+
}): Promise<DigestMessage[]> {
|
|
64
|
+
if (params.workstreamIds.length === 0) return []
|
|
65
|
+
|
|
66
|
+
let query: BoundQuery | null = null
|
|
67
|
+
if (params.cursor) {
|
|
68
|
+
const cursorRowId = ensureRecordId(params.cursor.id, TABLES.WORKSTREAM_MESSAGE)
|
|
69
|
+
query = new BoundQuery(
|
|
70
|
+
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
71
|
+
WHERE workstreamId IN $workstreamIds
|
|
72
|
+
AND (
|
|
73
|
+
createdAt > $cursorCreatedAt
|
|
74
|
+
OR (createdAt = $cursorCreatedAt AND id > $cursorRowId)
|
|
75
|
+
)
|
|
76
|
+
ORDER BY createdAt ASC, id ASC`,
|
|
77
|
+
{ workstreamIds: params.workstreamIds, cursorCreatedAt: params.cursor.createdAt, cursorRowId },
|
|
78
|
+
)
|
|
79
|
+
} else if (params.onboardingCutoff) {
|
|
80
|
+
query = new BoundQuery(
|
|
81
|
+
`SELECT type::string(id) AS id, type::string(workstreamId) AS workstreamId, role, parts, metadata, createdAt FROM ${TABLES.WORKSTREAM_MESSAGE}
|
|
82
|
+
WHERE workstreamId IN $workstreamIds
|
|
83
|
+
AND createdAt > $onboardingCutoff
|
|
84
|
+
ORDER BY createdAt ASC, id ASC`,
|
|
85
|
+
{ workstreamIds: params.workstreamIds, onboardingCutoff: params.onboardingCutoff },
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!query) return []
|
|
90
|
+
|
|
91
|
+
const rows = await databaseService.query<unknown>(query)
|
|
92
|
+
return rows.map((row) => mapWorkstreamRow(WorkstreamMessageRowSchema.parse(row)))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function normalizeBlock(value: string): string {
|
|
96
|
+
return normalizeTextBody(value)
|
|
97
|
+
}
|
|
@@ -4,6 +4,11 @@ import { fileURLToPath } from 'node:url'
|
|
|
4
4
|
import type { Job, Worker } from 'bullmq'
|
|
5
5
|
|
|
6
6
|
import { chatLogger } from '../config/logger'
|
|
7
|
+
import { truncateText } from '../utils/string'
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_JOB_RETENTION = { removeOnComplete: 200, removeOnFail: 200 }
|
|
10
|
+
export const LOW_JOB_RETENTION = { removeOnComplete: 50, removeOnFail: 50 }
|
|
11
|
+
export const LONG_JOB_LOCK_DURATION_MS = 600_000
|
|
7
12
|
|
|
8
13
|
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000
|
|
9
14
|
const MAX_TRACE_STRING_CHARS = 2_000
|
|
@@ -28,8 +33,7 @@ interface TracedWorkerJobLike {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
function truncateTraceString(value: string, maxChars = MAX_TRACE_STRING_CHARS): string {
|
|
31
|
-
|
|
32
|
-
return `${value.slice(0, maxChars - 3)}...`
|
|
36
|
+
return truncateText(value, maxChars)
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
function normalizeTraceValue(value: unknown, depth = 0): unknown {
|