@lota-sdk/core 0.1.15 → 0.1.17
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 +12 -8
- package/src/ai/definitions.ts +81 -3
- package/src/ai/embedding-cache.ts +2 -4
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/bifrost/cache-headers.ts +8 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +31 -21
- 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/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +269 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.helpers.ts +1 -3
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/schema-fingerprint.ts +1 -3
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- 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/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +2 -4
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +20 -55
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -2
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +109 -35
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +24 -64
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +3 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +111 -14
- package/src/runtime/runtime-extensions.ts +2 -3
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/social-chat.ts +752 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -32
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +292 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- 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 +2 -4
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +27 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +49 -61
- 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 +11 -4
- 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-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 +384 -40
- 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 +84 -2
- 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.service.ts +28 -34
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/social-chat-history.service.ts +197 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +13 -37
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -89
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/context-compaction.agent.ts +2 -0
- package/src/system-agents/delegated-agent-factory.ts +5 -0
- package/src/system-agents/memory-reranker.agent.ts +4 -2
- package/src/system-agents/memory.agent.ts +2 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
- package/src/system-agents/skill-extractor.agent.ts +2 -0
- package/src/system-agents/skill-manager.agent.ts +2 -0
- package/src/system-agents/title-generator.agent.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/research-topic.tool.ts +2 -0
- package/src/tools/team-think.tool.ts +5 -6
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
- package/src/workers/skill-extraction.runner.ts +26 -6
- package/src/workers/utils/file-section-chunker.ts +2 -1
- package/src/workers/utils/repo-structure-extractor.ts +2 -2
- 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 +14 -25
- package/src/workers/worker-utils.ts +2 -2
- package/src/runtime/workstream-routing-policy.ts +0 -267
- package/src/tools/log-hello-world.tool.ts +0 -17
package/src/db/startup.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordIdSchema } from '@lota-sdk/shared'
|
|
1
2
|
import { BoundQuery, RecordId } from 'surrealdb'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
@@ -11,11 +12,11 @@ const DEFAULT_MAX_WAIT_MS = 3 * 60 * 1_000
|
|
|
11
12
|
const RETRY_LOG_INTERVAL = 5
|
|
12
13
|
|
|
13
14
|
const RuntimeBootstrapRecordSchema = z.object({
|
|
14
|
-
id:
|
|
15
|
+
id: recordIdSchema,
|
|
15
16
|
key: z.string(),
|
|
16
17
|
schemaFingerprint: z.string(),
|
|
17
|
-
readyAt: z.
|
|
18
|
-
updatedAt: z.
|
|
18
|
+
readyAt: z.coerce.date(),
|
|
19
|
+
updatedAt: z.coerce.date().optional().nullable(),
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
type StartupLogger = Pick<SurrealDatabaseLogger, 'info' | 'warn' | 'error'>
|
|
@@ -62,7 +63,7 @@ export async function connectWithStartupRetry(params: {
|
|
|
62
63
|
async function readDatabaseBootstrapRecord(
|
|
63
64
|
databaseService: SurrealDBService,
|
|
64
65
|
): Promise<z.infer<typeof RuntimeBootstrapRecordSchema> | null> {
|
|
65
|
-
return
|
|
66
|
+
return databaseService.queryOne(
|
|
66
67
|
new BoundQuery(
|
|
67
68
|
`SELECT *
|
|
68
69
|
FROM ${TABLES.RUNTIME_BOOTSTRAP}
|
|
@@ -141,12 +142,7 @@ export async function publishDatabaseBootstrap(params: {
|
|
|
141
142
|
await params.databaseService.upsert(
|
|
142
143
|
TABLES.RUNTIME_BOOTSTRAP,
|
|
143
144
|
new RecordId(TABLES.RUNTIME_BOOTSTRAP, DATABASE_BOOTSTRAP_KEY),
|
|
144
|
-
{
|
|
145
|
-
key: DATABASE_BOOTSTRAP_KEY,
|
|
146
|
-
schemaFingerprint: params.schemaFingerprint,
|
|
147
|
-
readyAt: new Date(),
|
|
148
|
-
updatedAt: new Date(),
|
|
149
|
-
},
|
|
145
|
+
{ key: DATABASE_BOOTSTRAP_KEY, schemaFingerprint: params.schemaFingerprint, readyAt: new Date() },
|
|
150
146
|
RuntimeBootstrapRecordSchema,
|
|
151
147
|
)
|
|
152
148
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type SurrealSetClause = { bindingEntries: Record<string, unknown>; statement: string }
|
|
2
|
+
|
|
3
|
+
export function buildRequiredSurrealSetClause(field: string, bindingName: string, value: unknown): SurrealSetClause {
|
|
4
|
+
return { statement: `${field} = $${bindingName}`, bindingEntries: { [bindingName]: value } }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildOptionalSurrealSetClause(field: string, value: unknown): SurrealSetClause
|
|
8
|
+
export function buildOptionalSurrealSetClause(field: string, bindingName: string, value: unknown): SurrealSetClause
|
|
9
|
+
export function buildOptionalSurrealSetClause(
|
|
10
|
+
field: string,
|
|
11
|
+
bindingNameOrValue: unknown,
|
|
12
|
+
value?: unknown,
|
|
13
|
+
): SurrealSetClause {
|
|
14
|
+
const hasExplicitBinding = arguments.length === 3
|
|
15
|
+
const resolvedValue = hasExplicitBinding ? value : bindingNameOrValue
|
|
16
|
+
const resolvedBindingName = hasExplicitBinding
|
|
17
|
+
? (bindingNameOrValue as string)
|
|
18
|
+
: `resource_${field.replace(/[^a-zA-Z0-9]+/g, '_')}`
|
|
19
|
+
|
|
20
|
+
if (resolvedValue === null || resolvedValue === undefined) {
|
|
21
|
+
return { statement: `${field} = NONE`, bindingEntries: {} }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return buildRequiredSurrealSetClause(field, resolvedBindingName, resolvedValue)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function combineSurrealSetClauses(...clauses: SurrealSetClause[]): {
|
|
28
|
+
bindings: Record<string, unknown>
|
|
29
|
+
statement: string
|
|
30
|
+
} {
|
|
31
|
+
const bindings: Record<string, unknown> = {}
|
|
32
|
+
for (const clause of clauses) {
|
|
33
|
+
for (const [key, value] of Object.entries(clause.bindingEntries)) {
|
|
34
|
+
bindings[key] = value
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { bindings, statement: clauses.map((clause) => clause.statement).join(', ') }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function compactDefinedRecord<T extends Record<string, unknown>>(value: T): Partial<T> {
|
|
42
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as Partial<T>
|
|
43
|
+
}
|
package/src/db/tables.ts
CHANGED
|
@@ -17,11 +17,18 @@ export const TABLES = {
|
|
|
17
17
|
PLAN_CHECKPOINT: 'planCheckpoint',
|
|
18
18
|
PLAN_VALIDATION_ISSUE: 'planValidationIssue',
|
|
19
19
|
PLAN_EVENT: 'planEvent',
|
|
20
|
+
PLAN_SCHEDULE: 'planSchedule',
|
|
21
|
+
PLAN_TEMPLATE: 'planTemplate',
|
|
22
|
+
PLAN_CYCLE: 'planCycle',
|
|
20
23
|
ORGANIZATION: 'organization',
|
|
21
24
|
ORGANIZATION_MEMBER: 'organizationMember',
|
|
22
25
|
USER: 'user',
|
|
23
26
|
RECENT_ACTIVITY_EVENT: 'recentActivityEvent',
|
|
24
27
|
RECENT_ACTIVITY: 'recentActivity',
|
|
28
|
+
PLAYBOOK: 'playbook',
|
|
29
|
+
PLAYBOOK_VERSION: 'playbookVersion',
|
|
30
|
+
INSTITUTIONAL_MEMORY: 'institutionalMemory',
|
|
31
|
+
QUALITY_METRIC: 'qualityMetric',
|
|
25
32
|
} as const
|
|
26
33
|
|
|
27
34
|
export type DatabaseTable = (typeof TABLES)[keyof typeof TABLES] | (string & {})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { recordIdSchema } from '@lota-sdk/shared'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const WorkstreamMessageRowSchema = z.object({
|
|
5
|
+
id: recordIdSchema,
|
|
6
|
+
workstreamId: recordIdSchema,
|
|
7
|
+
messageId: z.string(),
|
|
8
|
+
role: z.enum(['system', 'user', 'assistant']),
|
|
9
|
+
parts: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
10
|
+
metadata: z.record(z.string(), z.unknown()).nullish(),
|
|
11
|
+
createdAt: z.coerce.date(),
|
|
12
|
+
updatedAt: z.coerce.date().optional(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export type WorkstreamMessageRow = z.infer<typeof WorkstreamMessageRowSchema>
|
|
@@ -71,7 +71,7 @@ export class ProviderEmbeddings {
|
|
|
71
71
|
const redisCache = this.getCache()
|
|
72
72
|
if (!redisCache) return null
|
|
73
73
|
|
|
74
|
-
return
|
|
74
|
+
return redisCache.get(this.getModelId(), text)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
async embedQuery(text: string): Promise<number[]> {
|
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import { Queue, Worker } from 'bullmq'
|
|
2
1
|
import type { Job } from 'bullmq'
|
|
3
2
|
|
|
4
|
-
import { serverLogger } from '../config/logger'
|
|
5
3
|
import { ensureRecordId } from '../db/record-id'
|
|
6
4
|
import { databaseService } from '../db/service'
|
|
7
5
|
import { TABLES } from '../db/tables'
|
|
8
|
-
import { getRedisConnectionForBullMQ } from '../redis'
|
|
9
6
|
import { contextCompactionService } from '../services/context-compaction.service'
|
|
10
7
|
import { workstreamService } from '../services/workstream.service'
|
|
11
|
-
import {
|
|
12
|
-
attachWorkerEvents,
|
|
13
|
-
createTracedWorkerProcessor,
|
|
14
|
-
createWorkerShutdown,
|
|
15
|
-
DEFAULT_JOB_RETENTION,
|
|
16
|
-
registerShutdownSignals,
|
|
17
|
-
} from '../workers/worker-utils'
|
|
18
|
-
import type { WorkerHandle } from '../workers/worker-utils'
|
|
8
|
+
import { createQueueFactory } from './queue-factory'
|
|
19
9
|
|
|
20
10
|
interface ContextCompactionJob {
|
|
21
11
|
domain: 'workstream'
|
|
@@ -23,25 +13,6 @@ interface ContextCompactionJob {
|
|
|
23
13
|
contextSize?: number
|
|
24
14
|
}
|
|
25
15
|
|
|
26
|
-
const CONTEXT_COMPACTION_QUEUE = 'context-compaction'
|
|
27
|
-
|
|
28
|
-
let _contextCompactionQueue: Queue<ContextCompactionJob> | null = null
|
|
29
|
-
function getContextCompactionQueue(): Queue<ContextCompactionJob> {
|
|
30
|
-
if (!_contextCompactionQueue) {
|
|
31
|
-
_contextCompactionQueue = new Queue<ContextCompactionJob>(CONTEXT_COMPACTION_QUEUE, {
|
|
32
|
-
connection: getRedisConnectionForBullMQ(),
|
|
33
|
-
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 3_000 } },
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
return _contextCompactionQueue
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function enqueueContextCompaction(job: ContextCompactionJob) {
|
|
40
|
-
return await getContextCompactionQueue().add('compact', job, {
|
|
41
|
-
deduplication: { id: `compact:${job.domain}:${job.entityId}` },
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
16
|
async function processContextCompactionJob(job: Job<ContextCompactionJob>): Promise<void> {
|
|
46
17
|
await databaseService.connect()
|
|
47
18
|
|
|
@@ -55,24 +26,22 @@ async function processContextCompactionJob(job: Job<ContextCompactionJob>): Prom
|
|
|
55
26
|
}
|
|
56
27
|
}
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
registerShutdownSignals({ name: 'Context compaction', shutdown, logger: serverLogger })
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { worker, shutdown }
|
|
29
|
+
const contextCompaction = createQueueFactory<ContextCompactionJob>({
|
|
30
|
+
name: 'context-compaction',
|
|
31
|
+
displayName: 'Context compaction',
|
|
32
|
+
jobName: 'compact',
|
|
33
|
+
concurrency: 2,
|
|
34
|
+
lockDuration: 300_000,
|
|
35
|
+
defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 3_000 } },
|
|
36
|
+
processor: processContextCompactionJob,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export function enqueueContextCompaction(job: ContextCompactionJob) {
|
|
40
|
+
return contextCompaction.enqueue(job, { deduplication: { id: `compact:${job.domain}:${job.entityId}` } })
|
|
74
41
|
}
|
|
75
42
|
|
|
43
|
+
export const startContextCompactionWorker = contextCompaction.startWorker
|
|
44
|
+
|
|
76
45
|
if (import.meta.main) {
|
|
77
46
|
startContextCompactionWorker()
|
|
78
47
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Job } from 'bullmq'
|
|
2
|
+
|
|
3
|
+
import { databaseService } from '../db/service'
|
|
4
|
+
import { planExecutorService } from '../services/plan-executor.service'
|
|
5
|
+
import { createQueueFactory } from './queue-factory'
|
|
6
|
+
|
|
7
|
+
export interface DelayedNodePromotionJob {
|
|
8
|
+
runId: string
|
|
9
|
+
nodeId: string
|
|
10
|
+
emittedBy: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DELAYED_NODE_PROMOTION_QUEUE = 'delayed-node-promotion'
|
|
14
|
+
|
|
15
|
+
async function processDelayedNodePromotionJob(job: Job<DelayedNodePromotionJob>): Promise<void> {
|
|
16
|
+
await databaseService.connect()
|
|
17
|
+
await planExecutorService.promoteDelayedNode({
|
|
18
|
+
runId: job.data.runId,
|
|
19
|
+
nodeId: job.data.nodeId,
|
|
20
|
+
emittedBy: job.data.emittedBy,
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const delayedNodePromotion = createQueueFactory<DelayedNodePromotionJob>({
|
|
25
|
+
name: DELAYED_NODE_PROMOTION_QUEUE,
|
|
26
|
+
displayName: 'Delayed node promotion',
|
|
27
|
+
jobName: 'promote-node',
|
|
28
|
+
concurrency: 1,
|
|
29
|
+
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000 } },
|
|
30
|
+
processor: processDelayedNodePromotionJob,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export async function enqueueDelayedNodePromotion(job: DelayedNodePromotionJob, delayMs: number) {
|
|
34
|
+
await delayedNodePromotion.enqueue(job, { delay: delayMs, jobId: `promote:${job.runId}:${job.nodeId}` })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const startDelayedNodePromotionWorker = delayedNodePromotion.startWorker
|
|
38
|
+
|
|
39
|
+
if (import.meta.main) {
|
|
40
|
+
startDelayedNodePromotionWorker()
|
|
41
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
1
|
import { Queue, Worker } from 'bullmq'
|
|
4
2
|
import type IORedis from 'ioredis'
|
|
5
3
|
|
|
@@ -43,7 +41,7 @@ export function buildDocumentProcessorJobId(
|
|
|
43
41
|
'orgId' | 'source' | 'sourceId' | 'sourceCanonicalKey' | 'sourceVersionKey' | 'title'
|
|
44
42
|
>,
|
|
45
43
|
): string {
|
|
46
|
-
const digest =
|
|
44
|
+
const digest = new Bun.CryptoHasher('sha256')
|
|
47
45
|
.update(
|
|
48
46
|
JSON.stringify({
|
|
49
47
|
orgId: job.orgId,
|
|
@@ -73,7 +71,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
|
|
|
73
71
|
} {
|
|
74
72
|
const queueName = params.queueName ?? DEFAULT_DOCUMENT_PROCESSOR_QUEUE
|
|
75
73
|
const workerName = params.workerName ?? DEFAULT_WORKER_NAME
|
|
76
|
-
const concurrency = params.concurrency ??
|
|
74
|
+
const concurrency = params.concurrency ?? 10
|
|
77
75
|
const lockDuration = params.lockDuration ?? 300_000
|
|
78
76
|
const jobName = 'process-document' as Parameters<Queue<TJob, unknown, string>['add']>[0]
|
|
79
77
|
const toQueueData = (job: TJob): Parameters<Queue<TJob, unknown, string>['add']>[1] =>
|
package/src/queues/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export * from './queue-factory'
|
|
1
2
|
export * from './context-compaction.queue'
|
|
3
|
+
export * from './delayed-node-promotion.queue'
|
|
2
4
|
export * from './document-processor.queue'
|
|
3
5
|
export * from './memory-consolidation.queue'
|
|
6
|
+
export * from './plan-scheduler.queue'
|
|
4
7
|
export * from './post-chat-memory.queue'
|
|
5
8
|
export * from './recent-activity-title-refinement.queue'
|
|
6
9
|
export * from './regular-chat-memory-digest.config'
|
|
@@ -1,69 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { serverLogger } from '../config/logger'
|
|
4
|
-
import { getRedisConnectionForBullMQ } from '../redis'
|
|
5
|
-
import {
|
|
6
|
-
attachWorkerEvents,
|
|
7
|
-
getWorkerPath,
|
|
8
|
-
LONG_JOB_LOCK_DURATION_MS,
|
|
9
|
-
LOW_JOB_RETENTION,
|
|
10
|
-
createWorkerShutdown,
|
|
11
|
-
registerShutdownSignals,
|
|
12
|
-
} from '../workers/worker-utils'
|
|
13
|
-
import type { WorkerHandle } from '../workers/worker-utils'
|
|
1
|
+
import { getWorkerPath, LONG_JOB_LOCK_DURATION_MS, LOW_JOB_RETENTION } from '../workers/worker-utils'
|
|
2
|
+
import { createQueueFactory } from './queue-factory'
|
|
14
3
|
|
|
15
4
|
export interface MemoryConsolidationJob {
|
|
16
5
|
scopeId?: string
|
|
17
6
|
}
|
|
18
7
|
|
|
19
|
-
const MEMORY_CONSOLIDATION_QUEUE = 'memory-consolidation'
|
|
20
8
|
const MEMORY_CONSOLIDATION_INTERVAL_MS = 24 * 60 * 60 * 1000
|
|
21
9
|
const MEMORY_CONSOLIDATION_JOB_ID = 'memory-consolidation-recurring'
|
|
22
10
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
11
|
+
const memoryConsolidation = createQueueFactory<MemoryConsolidationJob>({
|
|
12
|
+
name: 'memory-consolidation',
|
|
13
|
+
displayName: 'Memory consolidation',
|
|
14
|
+
jobName: 'consolidate-turn',
|
|
15
|
+
concurrency: 1,
|
|
16
|
+
lockDuration: LONG_JOB_LOCK_DURATION_MS,
|
|
17
|
+
defaultJobOptions: { ...LOW_JOB_RETENTION, attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
|
|
18
|
+
processorPath: getWorkerPath('memory-consolidation.worker.ts'),
|
|
19
|
+
})
|
|
33
20
|
|
|
34
21
|
export async function enqueueMemoryConsolidation(job: MemoryConsolidationJob = {}) {
|
|
35
|
-
await
|
|
36
|
-
jobId: job.scopeId ? `consolidate-turn:${job.scopeId}` : undefined,
|
|
37
|
-
})
|
|
22
|
+
await memoryConsolidation.enqueue(job, { jobId: job.scopeId ? `consolidate-turn:${job.scopeId}` : undefined })
|
|
38
23
|
}
|
|
39
24
|
|
|
40
25
|
export async function scheduleRecurringConsolidation() {
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
{},
|
|
44
|
-
{ repeat: { every: MEMORY_CONSOLIDATION_INTERVAL_MS }, jobId: MEMORY_CONSOLIDATION_JOB_ID },
|
|
45
|
-
)
|
|
26
|
+
await memoryConsolidation
|
|
27
|
+
.getQueue()
|
|
28
|
+
.add('consolidate', {}, { repeat: { every: MEMORY_CONSOLIDATION_INTERVAL_MS }, jobId: MEMORY_CONSOLIDATION_JOB_ID })
|
|
46
29
|
}
|
|
47
30
|
|
|
48
|
-
export
|
|
49
|
-
const { registerSignals = import.meta.main } = options
|
|
50
|
-
const processorPath = getWorkerPath('memory-consolidation.worker.ts')
|
|
51
|
-
const worker = new Worker(MEMORY_CONSOLIDATION_QUEUE, processorPath, {
|
|
52
|
-
connection: getRedisConnectionForBullMQ(),
|
|
53
|
-
lockDuration: LONG_JOB_LOCK_DURATION_MS,
|
|
54
|
-
concurrency: 1,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
attachWorkerEvents(worker, 'Memory consolidation', serverLogger)
|
|
58
|
-
|
|
59
|
-
const shutdown = createWorkerShutdown(worker, 'Memory consolidation', serverLogger)
|
|
60
|
-
|
|
61
|
-
if (registerSignals) {
|
|
62
|
-
registerShutdownSignals({ name: 'Memory consolidation', shutdown, logger: serverLogger })
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return { worker, shutdown }
|
|
66
|
-
}
|
|
31
|
+
export const startMemoryConsolidationWorker = memoryConsolidation.startWorker
|
|
67
32
|
|
|
68
33
|
if (import.meta.main) {
|
|
69
34
|
startMemoryConsolidationWorker()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Job } from 'bullmq'
|
|
2
|
+
|
|
3
|
+
import { serverLogger } from '../config/logger'
|
|
4
|
+
import { databaseService } from '../db/service'
|
|
5
|
+
import { planDeadlineService } from '../services/plan-deadline.service'
|
|
6
|
+
import { planSchedulerService } from '../services/plan-scheduler.service'
|
|
7
|
+
import type { WorkerHandle } from '../workers/worker-utils'
|
|
8
|
+
import { createQueueFactory } from './queue-factory'
|
|
9
|
+
|
|
10
|
+
export interface PlanSchedulerFireJob {
|
|
11
|
+
type: 'fire-schedule'
|
|
12
|
+
scheduleId: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PlanSchedulerDeadlineJob {
|
|
16
|
+
type: 'check-deadlines'
|
|
17
|
+
scheduledFor?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PlanSchedulerJob = PlanSchedulerFireJob | PlanSchedulerDeadlineJob
|
|
21
|
+
|
|
22
|
+
export const PLAN_SCHEDULER_QUEUE = 'plan-scheduler'
|
|
23
|
+
|
|
24
|
+
async function processPlanSchedulerJob(job: Job<PlanSchedulerJob>): Promise<void> {
|
|
25
|
+
await databaseService.connect()
|
|
26
|
+
|
|
27
|
+
switch (job.data.type) {
|
|
28
|
+
case 'fire-schedule':
|
|
29
|
+
await planSchedulerService.fireScheduleById(job.data.scheduleId)
|
|
30
|
+
break
|
|
31
|
+
case 'check-deadlines':
|
|
32
|
+
await planDeadlineService.checkDeadlines()
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const planScheduler = createQueueFactory<PlanSchedulerJob>({
|
|
38
|
+
name: PLAN_SCHEDULER_QUEUE,
|
|
39
|
+
displayName: 'Plan scheduler',
|
|
40
|
+
jobName: 'plan-scheduler-job',
|
|
41
|
+
concurrency: 1,
|
|
42
|
+
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000 } },
|
|
43
|
+
processor: processPlanSchedulerJob,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/** Enqueue a delayed job that fires a specific schedule at its nextFireAt time. */
|
|
47
|
+
export async function enqueueScheduleFire(scheduleId: string, delayMs: number): Promise<void> {
|
|
48
|
+
await planScheduler.enqueue(
|
|
49
|
+
{ type: 'fire-schedule', scheduleId },
|
|
50
|
+
{ delay: Math.max(0, delayMs), jobId: `schedule:${scheduleId}`, removeOnComplete: true, removeOnFail: 50 },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Remove a pending fire job for a schedule (on cancel/pause/complete). */
|
|
55
|
+
export async function removeScheduleFireJob(scheduleId: string): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
await planScheduler.getQueue().remove(`schedule:${scheduleId}`)
|
|
58
|
+
} catch {
|
|
59
|
+
// Job may not exist (already fired or never enqueued) — safe to ignore
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEADLINE_CHECK_JOB_PREFIX = 'deadline-check'
|
|
64
|
+
|
|
65
|
+
export async function enqueueDeadlineCheck(scheduledFor: Date): Promise<void> {
|
|
66
|
+
const delay = Math.max(0, scheduledFor.getTime() - Date.now())
|
|
67
|
+
await planScheduler.enqueue(
|
|
68
|
+
{ type: 'check-deadlines', scheduledFor: scheduledFor.toISOString() },
|
|
69
|
+
{
|
|
70
|
+
delay,
|
|
71
|
+
jobId: `${DEADLINE_CHECK_JOB_PREFIX}:${scheduledFor.getTime()}`,
|
|
72
|
+
removeOnComplete: true,
|
|
73
|
+
removeOnFail: 50,
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function startPlanSchedulerWorker(options: { registerSignals?: boolean } = {}): WorkerHandle {
|
|
79
|
+
const handle = planScheduler.startWorker(options)
|
|
80
|
+
|
|
81
|
+
// Recover active schedules on startup
|
|
82
|
+
planSchedulerService.recoverActiveSchedules().catch((err: unknown) => {
|
|
83
|
+
serverLogger.error`Plan scheduler startup recovery failed: ${err}`
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Seed deadline checks from current runtime state. Subsequent checks are
|
|
87
|
+
// re-seeded by the queue job after each sweep and by schedule fire events.
|
|
88
|
+
planDeadlineService.recoverDeadlineChecks().catch((err: unknown) => {
|
|
89
|
+
serverLogger.error`Plan deadline recovery failed: ${err}`
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return handle
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (import.meta.main) {
|
|
96
|
+
startPlanSchedulerWorker()
|
|
97
|
+
}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
type OrganizationOnboardStatus = string
|
|
2
|
-
import { Queue, Worker } from 'bullmq'
|
|
3
2
|
import type { Job } from 'bullmq'
|
|
4
3
|
|
|
5
|
-
import { serverLogger } from '../config/logger'
|
|
6
4
|
import { databaseService } from '../db/service'
|
|
7
|
-
import { getRedisConnectionForBullMQ } from '../redis'
|
|
8
5
|
import { memoryService } from '../services/memory.service'
|
|
9
|
-
import {
|
|
10
|
-
attachWorkerEvents,
|
|
11
|
-
createTracedWorkerProcessor,
|
|
12
|
-
createWorkerShutdown,
|
|
13
|
-
DEFAULT_JOB_RETENTION,
|
|
14
|
-
registerShutdownSignals,
|
|
15
|
-
} from '../workers/worker-utils'
|
|
16
|
-
import type { WorkerHandle } from '../workers/worker-utils'
|
|
6
|
+
import { createQueueFactory } from './queue-factory'
|
|
17
7
|
|
|
18
8
|
interface PostChatMemoryMessage {
|
|
19
9
|
role: 'user' | 'agent'
|
|
@@ -25,6 +15,8 @@ interface PostChatMemoryExtractionJob {
|
|
|
25
15
|
orgId: string
|
|
26
16
|
workstreamId: string
|
|
27
17
|
sourceId: string
|
|
18
|
+
source?: string
|
|
19
|
+
sourceMetadata?: Record<string, unknown>
|
|
28
20
|
onboardStatus?: OrganizationOnboardStatus
|
|
29
21
|
userMessage: string
|
|
30
22
|
historyMessages: PostChatMemoryMessage[]
|
|
@@ -33,27 +25,6 @@ interface PostChatMemoryExtractionJob {
|
|
|
33
25
|
attachmentContext?: string
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
const POST_CHAT_MEMORY_QUEUE = 'post-chat-memory'
|
|
37
|
-
const POST_CHAT_MEMORY_CONCURRENCY = 3
|
|
38
|
-
const POST_CHAT_MEMORY_LOCK_DURATION_MS = 900_000
|
|
39
|
-
const POST_CHAT_MEMORY_MAX_STALLED_COUNT = 10
|
|
40
|
-
const POST_CHAT_MEMORY_STALLED_INTERVAL_MS = 120_000
|
|
41
|
-
|
|
42
|
-
let _postChatMemoryQueue: Queue<PostChatMemoryExtractionJob> | null = null
|
|
43
|
-
function getPostChatMemoryQueue(): Queue<PostChatMemoryExtractionJob> {
|
|
44
|
-
if (!_postChatMemoryQueue) {
|
|
45
|
-
_postChatMemoryQueue = new Queue<PostChatMemoryExtractionJob>(POST_CHAT_MEMORY_QUEUE, {
|
|
46
|
-
connection: getRedisConnectionForBullMQ(),
|
|
47
|
-
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
return _postChatMemoryQueue
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function enqueuePostChatMemory(job: PostChatMemoryExtractionJob) {
|
|
54
|
-
return await getPostChatMemoryQueue().add('extract-memory', job)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
28
|
async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>): Promise<void> {
|
|
58
29
|
await databaseService.connect()
|
|
59
30
|
|
|
@@ -85,6 +56,8 @@ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>):
|
|
|
85
56
|
input: userMessage,
|
|
86
57
|
output: joinedOutput,
|
|
87
58
|
sourceId: data.sourceId,
|
|
59
|
+
source: data.source,
|
|
60
|
+
sourceMetadata: data.sourceMetadata,
|
|
88
61
|
onboardStatus: data.onboardStatus,
|
|
89
62
|
...(uniqueAgentNames.length > 0 ? { agentName: uniqueAgentNames[0] } : {}),
|
|
90
63
|
historyMessages: data.historyMessages,
|
|
@@ -94,30 +67,22 @@ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>):
|
|
|
94
67
|
})
|
|
95
68
|
}
|
|
96
69
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
attachWorkerEvents(worker, 'Post-chat memory', serverLogger)
|
|
112
|
-
|
|
113
|
-
const shutdown = createWorkerShutdown(worker, 'Post-chat memory', serverLogger)
|
|
114
|
-
|
|
115
|
-
if (registerSignals) {
|
|
116
|
-
registerShutdownSignals({ name: 'Post-chat memory', shutdown, logger: serverLogger })
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return { worker, shutdown }
|
|
70
|
+
const postChatMemory = createQueueFactory<PostChatMemoryExtractionJob>({
|
|
71
|
+
name: 'post-chat-memory',
|
|
72
|
+
displayName: 'Post-chat memory',
|
|
73
|
+
jobName: 'extract-memory',
|
|
74
|
+
concurrency: 10,
|
|
75
|
+
lockDuration: 900_000,
|
|
76
|
+
maxStalledCount: 10,
|
|
77
|
+
stalledInterval: 120_000,
|
|
78
|
+
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
79
|
+
processor: processPostChatMemoryJob,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export function enqueuePostChatMemory(job: PostChatMemoryExtractionJob, options?: { dedupeKey?: string }) {
|
|
83
|
+
return postChatMemory.enqueue(job, options?.dedupeKey ? { jobId: options.dedupeKey } : undefined)
|
|
120
84
|
}
|
|
85
|
+
export const startPostChatMemoryWorker = postChatMemory.startWorker
|
|
121
86
|
|
|
122
87
|
if (import.meta.main) {
|
|
123
88
|
startPostChatMemoryWorker()
|