@lota-sdk/core 0.4.9 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ai/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +164 -82
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -107
- package/src/config/agent-types.ts +1 -1
- package/src/config/background-processing.ts +1 -1
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +22 -25
- package/src/config/thread-defaults.ts +1 -10
- package/src/create-runtime.ts +145 -670
- package/src/db/base.service.ts +30 -38
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.ts +29 -20
- package/src/db/memory.ts +188 -195
- package/src/db/service-normalization.ts +97 -64
- package/src/db/service.ts +496 -384
- package/src/db/startup.ts +30 -19
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -7
- package/src/effect/layers.ts +75 -72
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -71
- package/src/index.ts +13 -12
- package/src/queues/autonomous-job.queue.ts +177 -143
- package/src/queues/context-compaction.queue.ts +41 -39
- package/src/queues/delayed-node-promotion.queue.ts +61 -42
- package/src/queues/document-processor.queue.ts +5 -3
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +79 -53
- package/src/queues/organization-learning.queue.ts +70 -33
- package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
- package/src/queues/plan-scheduler.queue.ts +101 -97
- package/src/queues/post-chat-memory.queue.ts +56 -46
- package/src/queues/queue-factory.ts +146 -69
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +44 -44
- package/src/redis/connection.ts +181 -164
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +10 -5
- package/src/runtime/agent-stream-helpers.ts +24 -15
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +131 -85
- package/src/runtime/domain-layer.ts +203 -0
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -14
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/memory/memory-block.ts +19 -9
- package/src/runtime/memory/memory-pipeline.ts +53 -66
- package/src/runtime/memory/memory-scope.ts +33 -29
- package/src/runtime/plugin-resolution.ts +58 -62
- package/src/runtime/post-turn-side-effects.ts +139 -161
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +0 -43
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +455 -0
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
- package/src/runtime/social-chat/social-chat-history.ts +24 -13
- package/src/runtime/social-chat/social-chat.ts +420 -369
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
- package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
- package/src/runtime/thread-chat-helpers.ts +18 -9
- package/src/runtime/thread-turn-context.ts +28 -74
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +169 -176
- package/src/services/agent-executor.service.ts +207 -196
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +16 -48
- package/src/services/autonomous-job.service.ts +81 -87
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +3 -1
- package/src/services/context-compaction.service.ts +8 -10
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +122 -109
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +68 -51
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +49 -15
- package/src/services/graph-full-routing.ts +49 -37
- package/src/services/index.ts +1 -0
- package/src/services/institutional-memory.service.ts +8 -17
- package/src/services/learned-skill.service.ts +38 -35
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +14 -3
- package/src/services/memory/memory-preseeded.ts +10 -4
- package/src/services/memory/memory-utils.ts +2 -1
- package/src/services/memory/memory.service.ts +37 -52
- package/src/services/memory/rerank.service.ts +3 -11
- package/src/services/monitoring-window.service.ts +1 -1
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +2 -2
- package/src/services/notification.service.ts +16 -4
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +34 -51
- package/src/services/ownership-dispatcher.service.ts +148 -95
- package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
- package/src/services/plan/plan-agent-query.service.ts +13 -9
- package/src/services/plan/plan-approval.service.ts +52 -48
- package/src/services/plan/plan-artifact.service.ts +2 -2
- package/src/services/plan/plan-builder.service.ts +2 -2
- package/src/services/plan/plan-checkpoint.service.ts +1 -1
- package/src/services/plan/plan-compiler.service.ts +1 -1
- package/src/services/plan/plan-completion-side-effects.ts +99 -113
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -202
- package/src/services/plan/plan-deadline.service.ts +304 -307
- package/src/services/plan/plan-event-delivery.service.ts +84 -72
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +375 -353
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +494 -489
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +89 -82
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +8 -5
- package/src/services/plan/plan-validator.service.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +17 -11
- package/src/services/plugin-executor.service.ts +26 -21
- package/src/services/quality-metrics.service.ts +1 -1
- package/src/services/queue-job.service.ts +8 -17
- package/src/services/recent-activity-title.service.ts +22 -10
- package/src/services/recent-activity.service.ts +1 -1
- package/src/services/skill-resolver.service.ts +1 -1
- package/src/services/social-chat-history.service.ts +37 -20
- package/src/services/system-executor.service.ts +25 -20
- package/src/services/thread/thread-bootstrap.ts +37 -19
- package/src/services/thread/thread-listing.ts +2 -1
- package/src/services/thread/thread-memory-block.ts +18 -5
- package/src/services/thread/thread-message.service.ts +30 -13
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +87 -83
- package/src/services/thread/thread-turn-preparation.service.ts +65 -40
- package/src/services/thread/thread-turn-streaming.ts +32 -36
- package/src/services/thread/thread-turn.ts +43 -29
- package/src/services/thread/thread.service.ts +32 -8
- package/src/services/user.service.ts +1 -1
- package/src/services/write-intent-validator.service.ts +1 -1
- package/src/storage/attachment-storage.service.ts +7 -4
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +1 -1
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +1 -1
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/system-agents/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +36 -20
- package/src/tools/fetch-webpage.tool.ts +30 -22
- package/src/tools/firecrawl-client.ts +1 -6
- package/src/tools/plan-approval.tool.ts +9 -1
- package/src/tools/remember-memory.tool.ts +3 -6
- package/src/tools/research-topic.tool.ts +12 -3
- package/src/tools/search-web.tool.ts +26 -18
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +34 -58
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +16 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +14 -8
- package/src/config/search.ts +0 -3
- package/src/effect/awaitable-effect.ts +0 -87
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -31
- package/src/redis/runtime-connection.ts +0 -10
- package/src/runtime/agent-types.ts +0 -1
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import type { Job } from 'bullmq'
|
|
2
|
-
import { Effect } from 'effect'
|
|
2
|
+
import { Effect, Schema } from 'effect'
|
|
3
3
|
import type { Context } from 'effect'
|
|
4
4
|
import type IORedis from 'ioredis'
|
|
5
5
|
|
|
6
6
|
import { serverLogger } from '../config/logger'
|
|
7
|
-
import { ConfigurationError } from '../effect/errors'
|
|
8
7
|
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
9
|
-
import {
|
|
10
|
-
import type { WorkerHandle } from '../workers/worker-utils'
|
|
8
|
+
import { QueueJobServiceTag } from '../services/queue-job.service'
|
|
9
|
+
import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
|
|
11
10
|
import { DEFAULT_JOB_RETENTION, LONG_JOB_LOCK_DURATION_MS } from '../workers/worker-utils'
|
|
12
|
-
import {
|
|
11
|
+
import { createQueueFactoryWithDeps } from './queue-factory'
|
|
13
12
|
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
14
13
|
|
|
14
|
+
// Minimal service shape used by the worker processor. Declared structurally to
|
|
15
|
+
// avoid importing the service tag — which would form a dependency cycle since
|
|
16
|
+
// PlanAgentHeartbeatServiceLive depends on LotaQueuesServiceTag.
|
|
17
|
+
interface PlanAgentHeartbeatWorkerServiceShape {
|
|
18
|
+
wakeNode(params: PlanAgentHeartbeatWakeJob): Effect.Effect<unknown, unknown, unknown>
|
|
19
|
+
sweep(params: { organizationId?: string }): Effect.Effect<void, unknown, unknown>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class PlanAgentHeartbeatQueueError extends Schema.TaggedErrorClass<PlanAgentHeartbeatQueueError>()(
|
|
23
|
+
'@lota-sdk/core/PlanAgentHeartbeatQueueError',
|
|
24
|
+
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
25
|
+
) {}
|
|
26
|
+
|
|
15
27
|
export interface PlanAgentHeartbeatWakeJob {
|
|
16
28
|
type: 'wake-node'
|
|
17
29
|
organizationId: string
|
|
@@ -33,58 +45,11 @@ export const PLAN_AGENT_HEARTBEAT_QUEUE = 'plan-agent-heartbeat'
|
|
|
33
45
|
const PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS = 30_000
|
|
34
46
|
const PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID = 'plan-agent-heartbeat-sweep'
|
|
35
47
|
|
|
36
|
-
interface
|
|
48
|
+
export interface PlanAgentHeartbeatWorkerDeps {
|
|
37
49
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
38
|
-
planAgentHeartbeatService:
|
|
50
|
+
planAgentHeartbeatService: PlanAgentHeartbeatWorkerServiceShape
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
let _deps: PlanAgentHeartbeatQueueDeps | null = null
|
|
42
|
-
function getDeps(): PlanAgentHeartbeatQueueDeps {
|
|
43
|
-
if (!_deps)
|
|
44
|
-
throw new ConfigurationError({
|
|
45
|
-
message: 'Plan agent heartbeat queue is not configured. Initialize the runtime before starting the worker.',
|
|
46
|
-
key: 'queue-deps',
|
|
47
|
-
})
|
|
48
|
-
return _deps
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function enqueueDelayedPlanAgentHeartbeatSweep(delayMs = PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS): Promise<void> {
|
|
52
|
-
return planAgentHeartbeatQueue.enqueue(
|
|
53
|
-
{ type: 'sweep' },
|
|
54
|
-
{ delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID },
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function processPlanAgentHeartbeatJob(job: Job<PlanAgentHeartbeatJob>): Promise<void> {
|
|
59
|
-
const { planAgentHeartbeatService } = getDeps()
|
|
60
|
-
|
|
61
|
-
return Effect.runPromise(
|
|
62
|
-
Effect.gen(function* () {
|
|
63
|
-
if (job.data.type === 'wake-node') {
|
|
64
|
-
const wakeJob = job.data
|
|
65
|
-
yield* planAgentHeartbeatService.wakeNode(wakeJob)
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
yield* planAgentHeartbeatService.sweep({ organizationId: job.data.organizationId })
|
|
70
|
-
if (!job.data.organizationId) {
|
|
71
|
-
yield* Effect.tryPromise(() => enqueueDelayedPlanAgentHeartbeatSweep())
|
|
72
|
-
}
|
|
73
|
-
}),
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const planAgentHeartbeatQueue = createQueueFactory<PlanAgentHeartbeatJob>({
|
|
78
|
-
name: PLAN_AGENT_HEARTBEAT_QUEUE,
|
|
79
|
-
displayName: 'Plan agent heartbeat',
|
|
80
|
-
jobName: 'plan-agent-heartbeat-job',
|
|
81
|
-
concurrency: 2,
|
|
82
|
-
lockDuration: LONG_JOB_LOCK_DURATION_MS,
|
|
83
|
-
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 5_000 } },
|
|
84
|
-
prepare: () => getDeps().databaseService.connect(),
|
|
85
|
-
processor: processPlanAgentHeartbeatJob,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
53
|
function buildWakeJobId(params: {
|
|
89
54
|
organizationId: string
|
|
90
55
|
threadId: string
|
|
@@ -97,42 +62,105 @@ function buildWakeJobId(params: {
|
|
|
97
62
|
return `plan-agent-wake__${encode(params.runId)}__${encode(params.nodeId)}__${encode(params.agentId)}`
|
|
98
63
|
}
|
|
99
64
|
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
65
|
+
export interface PlanAgentHeartbeatQueueRuntime {
|
|
66
|
+
enqueuePlanAgentHeartbeatWake(params: {
|
|
67
|
+
organizationId: string
|
|
68
|
+
threadId: string
|
|
69
|
+
runId: string
|
|
70
|
+
nodeId: string
|
|
71
|
+
agentId: string
|
|
72
|
+
reason: string
|
|
73
|
+
}): Promise<void>
|
|
74
|
+
startWorker(options: { registerSignals?: boolean; deps: PlanAgentHeartbeatWorkerDeps }): WorkerHandle
|
|
109
75
|
}
|
|
110
76
|
|
|
111
|
-
|
|
112
|
-
registerSignals?: boolean
|
|
77
|
+
interface MakePlanAgentHeartbeatQueueRuntimeParams {
|
|
113
78
|
connectionProvider: () => IORedis
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
_deps = options.deps
|
|
117
|
-
const handle = planAgentHeartbeatQueue.startWorker({
|
|
118
|
-
registerSignals: options.registerSignals,
|
|
119
|
-
connectionProvider: options.connectionProvider,
|
|
120
|
-
})
|
|
79
|
+
queueJobService: QueueJobService
|
|
80
|
+
}
|
|
121
81
|
|
|
122
|
-
|
|
123
|
-
|
|
82
|
+
export function makePlanAgentHeartbeatQueueRuntime(
|
|
83
|
+
params: MakePlanAgentHeartbeatQueueRuntimeParams,
|
|
84
|
+
): PlanAgentHeartbeatQueueRuntime {
|
|
85
|
+
const { connectionProvider, queueJobService } = params
|
|
86
|
+
|
|
87
|
+
const enqueueDelayedSweep = (delayMs = PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS): Promise<void> =>
|
|
88
|
+
queue.enqueue({ type: 'sweep' }, { delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID })
|
|
89
|
+
|
|
90
|
+
const processPlanAgentHeartbeatJob = (
|
|
91
|
+
deps: PlanAgentHeartbeatWorkerDeps,
|
|
92
|
+
job: Job<PlanAgentHeartbeatJob>,
|
|
93
|
+
): Promise<void> => {
|
|
94
|
+
const { planAgentHeartbeatService: service } = deps
|
|
95
|
+
return Effect.runPromise(
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
if (job.data.type === 'wake-node') {
|
|
98
|
+
const wakeJob = job.data
|
|
99
|
+
yield* service.wakeNode(wakeJob)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
yield* service.sweep({ organizationId: job.data.organizationId })
|
|
104
|
+
if (!job.data.organizationId) {
|
|
105
|
+
yield* Effect.tryPromise({
|
|
106
|
+
try: () => enqueueDelayedSweep(),
|
|
107
|
+
catch: (cause) =>
|
|
108
|
+
new PlanAgentHeartbeatQueueError({
|
|
109
|
+
message: 'Failed to enqueue delayed plan-agent heartbeat sweep.',
|
|
110
|
+
cause,
|
|
111
|
+
}),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}) as Effect.Effect<void, never, never>,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const queue = createQueueFactoryWithDeps<PlanAgentHeartbeatJob, PlanAgentHeartbeatWorkerDeps>({
|
|
119
|
+
name: PLAN_AGENT_HEARTBEAT_QUEUE,
|
|
120
|
+
displayName: 'Plan agent heartbeat',
|
|
121
|
+
jobName: 'plan-agent-heartbeat-job',
|
|
122
|
+
concurrency: 2,
|
|
123
|
+
lockDuration: LONG_JOB_LOCK_DURATION_MS,
|
|
124
|
+
defaultJobOptions: { ...DEFAULT_JOB_RETENTION, attempts: 3, backoff: { type: 'exponential', delay: 5_000 } },
|
|
125
|
+
connectionProvider,
|
|
126
|
+
queueJobService,
|
|
127
|
+
prepare: ({ databaseService }) => Effect.runPromise(databaseService.connect()),
|
|
128
|
+
processor: processPlanAgentHeartbeatJob,
|
|
124
129
|
})
|
|
125
130
|
|
|
126
|
-
return
|
|
131
|
+
return {
|
|
132
|
+
enqueuePlanAgentHeartbeatWake: (wakeParams) =>
|
|
133
|
+
queue.enqueue({ type: 'wake-node', ...wakeParams }, { jobId: buildWakeJobId(wakeParams) }),
|
|
134
|
+
startWorker: (options) => {
|
|
135
|
+
const handle = queue.startWorker({
|
|
136
|
+
deps: options.deps,
|
|
137
|
+
registerSignals: options.registerSignals,
|
|
138
|
+
connectionProvider,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
enqueueDelayedSweep().catch((error: unknown) => {
|
|
142
|
+
serverLogger.error`Plan agent heartbeat scheduler setup failed: ${error}`
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return handle
|
|
146
|
+
},
|
|
147
|
+
}
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
runStandaloneQueueWorker((runtime) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
void (async () => {
|
|
152
|
+
const { PlanAgentHeartbeatServiceTag } = await import('../services/plan/plan-agent-heartbeat.service')
|
|
153
|
+
const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
|
|
154
|
+
const redis = resolve(RedisServiceTag)
|
|
155
|
+
const planAgentHeartbeatQueue = makePlanAgentHeartbeatQueueRuntime({
|
|
156
|
+
connectionProvider: () => redis.getConnectionForBullMQ(),
|
|
157
|
+
queueJobService: resolve(QueueJobServiceTag),
|
|
158
|
+
})
|
|
159
|
+
planAgentHeartbeatQueue.startWorker({
|
|
160
|
+
deps: {
|
|
161
|
+
databaseService: resolve(DatabaseServiceTag),
|
|
162
|
+
planAgentHeartbeatService: resolve(PlanAgentHeartbeatServiceTag),
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
})()
|
|
138
166
|
})
|
|
@@ -4,15 +4,15 @@ import type { Context } from 'effect'
|
|
|
4
4
|
import type IORedis from 'ioredis'
|
|
5
5
|
|
|
6
6
|
import { serverLogger } from '../config/logger'
|
|
7
|
-
import { ConfigurationError } from '../effect/errors'
|
|
8
7
|
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
9
8
|
import { PlanCycleServiceTag } from '../services/plan/plan-cycle.service'
|
|
10
9
|
import { PlanDeadlineServiceTag } from '../services/plan/plan-deadline.service'
|
|
11
10
|
import { PlanExecutorServiceTag } from '../services/plan/plan-executor.service'
|
|
12
11
|
import { PlanSchedulerServiceTag } from '../services/plan/plan-scheduler.service'
|
|
12
|
+
import { QueueJobServiceTag } from '../services/queue-job.service'
|
|
13
13
|
import { nowEpochMillis } from '../utils/date-time'
|
|
14
|
-
import type { WorkerHandle } from '../workers/worker-utils'
|
|
15
|
-
import {
|
|
14
|
+
import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
|
|
15
|
+
import { createQueueFactoryWithDeps } from './queue-factory'
|
|
16
16
|
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
17
17
|
|
|
18
18
|
export interface PlanSchedulerFireJob {
|
|
@@ -29,7 +29,7 @@ export type PlanSchedulerJob = PlanSchedulerFireJob | PlanSchedulerDeadlineJob
|
|
|
29
29
|
|
|
30
30
|
export const PLAN_SCHEDULER_QUEUE = 'plan-scheduler'
|
|
31
31
|
|
|
32
|
-
interface
|
|
32
|
+
export interface PlanSchedulerWorkerDeps {
|
|
33
33
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
34
34
|
planSchedulerService: Context.Service.Shape<typeof PlanSchedulerServiceTag>
|
|
35
35
|
planDeadlineService: Context.Service.Shape<typeof PlanDeadlineServiceTag>
|
|
@@ -37,16 +37,6 @@ interface PlanSchedulerQueueDeps {
|
|
|
37
37
|
planCycleService: Context.Service.Shape<typeof PlanCycleServiceTag>
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
let _deps: PlanSchedulerQueueDeps | null = null
|
|
41
|
-
function getDeps(): PlanSchedulerQueueDeps {
|
|
42
|
-
if (!_deps)
|
|
43
|
-
throw new ConfigurationError({
|
|
44
|
-
message: 'Plan scheduler queue is not configured. Initialize the runtime before starting the worker.',
|
|
45
|
-
key: 'queue-deps',
|
|
46
|
-
})
|
|
47
|
-
return _deps
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
class PlanSchedulerQueueError extends Schema.TaggedErrorClass<PlanSchedulerQueueError>()('PlanSchedulerQueueError', {
|
|
51
41
|
stage: Schema.Literals(['remove-schedule-fire-job', 'recover-active-schedules', 'recover-deadline-checks']),
|
|
52
42
|
message: Schema.String,
|
|
@@ -57,111 +47,125 @@ function toPlanSchedulerQueueError(stage: PlanSchedulerQueueError['stage'], caus
|
|
|
57
47
|
return new PlanSchedulerQueueError({ stage, message: cause instanceof Error ? cause.message : String(cause), cause })
|
|
58
48
|
}
|
|
59
49
|
|
|
60
|
-
function processPlanSchedulerJob(job: Job<PlanSchedulerJob>): Promise<void> {
|
|
61
|
-
const { planSchedulerService, planDeadlineService, planExecutorService, planCycleService } =
|
|
50
|
+
function processPlanSchedulerJob(deps: PlanSchedulerWorkerDeps, job: Job<PlanSchedulerJob>): Promise<void> {
|
|
51
|
+
const { planSchedulerService, planDeadlineService, planExecutorService, planCycleService } = deps
|
|
52
|
+
const runWithResolvedContext = <A, E>(effect: Effect.Effect<A, E, unknown>): Promise<void> =>
|
|
53
|
+
// Service Effects carry their provided context through the managed runtime
|
|
54
|
+
// that resolved these service tags, so residual R collapses at runtime.
|
|
55
|
+
Effect.runPromise(Effect.asVoid(effect as Effect.Effect<A, E, never>))
|
|
62
56
|
|
|
63
57
|
switch (job.data.type) {
|
|
64
58
|
case 'fire-schedule':
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}),
|
|
72
|
-
),
|
|
59
|
+
return runWithResolvedContext(
|
|
60
|
+
planSchedulerService.fireScheduleById(job.data.scheduleId, {
|
|
61
|
+
promoteDelayedNode: (params) => runWithResolvedContext(planExecutorService.promoteDelayedNode(params)),
|
|
62
|
+
advanceCycle: (cycleId) => runWithResolvedContext(planCycleService.advanceCycle(cycleId)),
|
|
63
|
+
recoverDeadlineChecks: () => runWithResolvedContext(planDeadlineService.recoverDeadlineChecks()),
|
|
64
|
+
}),
|
|
73
65
|
)
|
|
74
66
|
case 'check-deadlines':
|
|
75
|
-
return
|
|
67
|
+
return runWithResolvedContext(planDeadlineService.checkDeadlines())
|
|
76
68
|
}
|
|
77
69
|
}
|
|
78
70
|
|
|
79
|
-
const planScheduler = createQueueFactory<PlanSchedulerJob>({
|
|
80
|
-
name: PLAN_SCHEDULER_QUEUE,
|
|
81
|
-
displayName: 'Plan scheduler',
|
|
82
|
-
jobName: 'plan-scheduler-job',
|
|
83
|
-
concurrency: 1,
|
|
84
|
-
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000 } },
|
|
85
|
-
prepare: () => getDeps().databaseService.connect(),
|
|
86
|
-
processor: processPlanSchedulerJob,
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
/** Enqueue a delayed job that fires a specific schedule at its nextFireAt time. */
|
|
90
|
-
export function enqueueScheduleFire(scheduleId: string, delayMs: number): Promise<void> {
|
|
91
|
-
return planScheduler.enqueue(
|
|
92
|
-
{ type: 'fire-schedule', scheduleId },
|
|
93
|
-
{ delay: Math.max(0, delayMs), jobId: `schedule:${scheduleId}`, removeOnComplete: true, removeOnFail: 50 },
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Remove a pending fire job for a schedule (on cancel/pause/complete). */
|
|
98
|
-
export function removeScheduleFireJob(scheduleId: string): Promise<void> {
|
|
99
|
-
return Effect.runPromise(
|
|
100
|
-
Effect.tryPromise({
|
|
101
|
-
try: () => planScheduler.getQueue().remove(`schedule:${scheduleId}`),
|
|
102
|
-
catch: (cause) => toPlanSchedulerQueueError('remove-schedule-fire-job', cause),
|
|
103
|
-
}).pipe(Effect.asVoid),
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
71
|
const DEADLINE_CHECK_JOB_PREFIX = 'deadline-check'
|
|
108
72
|
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
delay,
|
|
115
|
-
jobId: `${DEADLINE_CHECK_JOB_PREFIX}:${scheduledFor.getTime()}`,
|
|
116
|
-
removeOnComplete: true,
|
|
117
|
-
removeOnFail: 50,
|
|
118
|
-
},
|
|
119
|
-
)
|
|
73
|
+
export interface PlanSchedulerQueueRuntime {
|
|
74
|
+
enqueueScheduleFire(scheduleId: string, delayMs: number): Promise<void>
|
|
75
|
+
removeScheduleFireJob(scheduleId: string): Promise<void>
|
|
76
|
+
enqueueDeadlineCheck(scheduledFor: Date): Promise<void>
|
|
77
|
+
startWorker(options: { registerSignals?: boolean; deps: PlanSchedulerWorkerDeps }): WorkerHandle
|
|
120
78
|
}
|
|
121
79
|
|
|
122
|
-
|
|
123
|
-
registerSignals?: boolean
|
|
80
|
+
interface MakePlanSchedulerQueueRuntimeParams {
|
|
124
81
|
connectionProvider: () => IORedis
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
82
|
+
queueJobService: QueueJobService
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function makePlanSchedulerQueueRuntime(params: MakePlanSchedulerQueueRuntimeParams): PlanSchedulerQueueRuntime {
|
|
86
|
+
const { connectionProvider, queueJobService } = params
|
|
87
|
+
|
|
88
|
+
const queue = createQueueFactoryWithDeps<PlanSchedulerJob, PlanSchedulerWorkerDeps>({
|
|
89
|
+
name: PLAN_SCHEDULER_QUEUE,
|
|
90
|
+
displayName: 'Plan scheduler',
|
|
91
|
+
jobName: 'plan-scheduler-job',
|
|
92
|
+
concurrency: 1,
|
|
93
|
+
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000 } },
|
|
94
|
+
connectionProvider,
|
|
95
|
+
queueJobService,
|
|
96
|
+
prepare: ({ databaseService }) => Effect.runPromise(databaseService.connect()),
|
|
97
|
+
processor: processPlanSchedulerJob,
|
|
131
98
|
})
|
|
132
99
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
100
|
+
const enqueueScheduleFire: PlanSchedulerQueueRuntime['enqueueScheduleFire'] = (scheduleId, delayMs) =>
|
|
101
|
+
queue.enqueue(
|
|
102
|
+
{ type: 'fire-schedule', scheduleId },
|
|
103
|
+
{ delay: Math.max(0, delayMs), jobId: `schedule:${scheduleId}`, removeOnComplete: true, removeOnFail: 50 },
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const removeScheduleFireJob: PlanSchedulerQueueRuntime['removeScheduleFireJob'] = (scheduleId) =>
|
|
107
|
+
Effect.runPromise(
|
|
108
|
+
Effect.tryPromise({
|
|
109
|
+
try: () => queue.getQueue().remove(`schedule:${scheduleId}`),
|
|
110
|
+
catch: (cause) => toPlanSchedulerQueueError('remove-schedule-fire-job', cause),
|
|
111
|
+
}).pipe(Effect.asVoid),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const enqueueDeadlineCheck: PlanSchedulerQueueRuntime['enqueueDeadlineCheck'] = (scheduledFor) => {
|
|
115
|
+
const delay = Math.max(0, scheduledFor.getTime() - nowEpochMillis())
|
|
116
|
+
return queue.enqueue(
|
|
117
|
+
{ type: 'check-deadlines', scheduledFor: scheduledFor.toISOString() },
|
|
118
|
+
{
|
|
119
|
+
delay,
|
|
120
|
+
jobId: `${DEADLINE_CHECK_JOB_PREFIX}:${scheduledFor.getTime()}`,
|
|
121
|
+
removeOnComplete: true,
|
|
122
|
+
removeOnFail: 50,
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const startWorker: PlanSchedulerQueueRuntime['startWorker'] = (options) => {
|
|
128
|
+
const { deps } = options
|
|
129
|
+
const handle = queue.startWorker({ deps, registerSignals: options.registerSignals, connectionProvider })
|
|
130
|
+
|
|
131
|
+
// Recover active schedules on startup
|
|
132
|
+
void Effect.runFork(
|
|
133
|
+
deps.planSchedulerService.recoverActiveSchedules().pipe(
|
|
134
|
+
Effect.mapError((cause) => toPlanSchedulerQueueError('recover-active-schedules', cause)),
|
|
135
|
+
Effect.catchTag('PlanSchedulerQueueError', (error) =>
|
|
136
|
+
Effect.sync(() => {
|
|
137
|
+
serverLogger.error`Plan scheduler startup recovery failed: ${error.message}`
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
141
140
|
),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// Seed deadline checks from current runtime state.
|
|
144
|
+
void Effect.runFork(
|
|
145
|
+
deps.planDeadlineService.recoverDeadlineChecks().pipe(
|
|
146
|
+
Effect.mapError((cause) => toPlanSchedulerQueueError('recover-deadline-checks', cause)),
|
|
147
|
+
Effect.catchTag('PlanSchedulerQueueError', (error) =>
|
|
148
|
+
Effect.sync(() => {
|
|
149
|
+
serverLogger.error`Plan deadline recovery failed: ${error.message}`
|
|
150
|
+
}),
|
|
151
|
+
),
|
|
154
152
|
),
|
|
155
|
-
)
|
|
156
|
-
)
|
|
153
|
+
)
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
return handle
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { enqueueScheduleFire, removeScheduleFireJob, enqueueDeadlineCheck, startWorker }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
runStandaloneQueueWorker((runtime) => {
|
|
162
162
|
const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
const redis = resolve(RedisServiceTag)
|
|
164
|
+
const planSchedulerQueue = makePlanSchedulerQueueRuntime({
|
|
165
|
+
connectionProvider: () => redis.getConnectionForBullMQ(),
|
|
166
|
+
queueJobService: resolve(QueueJobServiceTag),
|
|
167
|
+
})
|
|
168
|
+
planSchedulerQueue.startWorker({
|
|
165
169
|
deps: {
|
|
166
170
|
databaseService: resolve(DatabaseServiceTag),
|
|
167
171
|
planSchedulerService: resolve(PlanSchedulerServiceTag),
|
|
@@ -3,11 +3,12 @@ import { Effect } from 'effect'
|
|
|
3
3
|
import type { Context } from 'effect'
|
|
4
4
|
import type IORedis from 'ioredis'
|
|
5
5
|
|
|
6
|
-
import type { MaybeAwaitableService } from '../effect/awaitable-effect'
|
|
7
|
-
import { ConfigurationError } from '../effect/errors'
|
|
8
6
|
import { DatabaseServiceTag, RedisServiceTag } from '../effect/services'
|
|
7
|
+
import type { RedisConnectionManager } from '../redis/connection'
|
|
9
8
|
import { MemoryServiceTag } from '../services/memory/memory.service'
|
|
10
|
-
import {
|
|
9
|
+
import { QueueJobServiceTag } from '../services/queue-job.service'
|
|
10
|
+
import type { QueueJobService, WorkerHandle } from '../workers/worker-utils'
|
|
11
|
+
import { createQueueFactoryWithDeps } from './queue-factory'
|
|
11
12
|
import { runStandaloneQueueWorker } from './standalone-worker'
|
|
12
13
|
|
|
13
14
|
interface PostChatMemoryMessage {
|
|
@@ -16,7 +17,7 @@ interface PostChatMemoryMessage {
|
|
|
16
17
|
agentName?: string
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
interface PostChatMemoryExtractionJob {
|
|
20
|
+
export interface PostChatMemoryExtractionJob {
|
|
20
21
|
orgId: string
|
|
21
22
|
threadId: string
|
|
22
23
|
sourceId: string
|
|
@@ -30,24 +31,17 @@ interface PostChatMemoryExtractionJob {
|
|
|
30
31
|
attachmentContext?: string
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
interface
|
|
34
|
+
export interface PostChatMemoryWorkerDeps {
|
|
34
35
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
35
|
-
memoryService:
|
|
36
|
+
memoryService: Context.Service.Shape<typeof MemoryServiceTag>
|
|
37
|
+
redisManager: RedisConnectionManager
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
key: 'queue-deps',
|
|
44
|
-
})
|
|
45
|
-
return _deps
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>): Promise<void> {
|
|
49
|
-
const { memoryService } = getDeps()
|
|
50
|
-
|
|
40
|
+
function processPostChatMemoryJob(
|
|
41
|
+
deps: PostChatMemoryWorkerDeps,
|
|
42
|
+
job: Job<PostChatMemoryExtractionJob>,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const { memoryService, redisManager } = deps
|
|
51
45
|
const data = job.data
|
|
52
46
|
const userMessage = data.userMessage.trim()
|
|
53
47
|
const agentMessages = data.agentMessages
|
|
@@ -89,43 +83,59 @@ function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>): Promis
|
|
|
89
83
|
attachmentContext: data.attachmentContext,
|
|
90
84
|
agentNames: uniqueAgentNames,
|
|
91
85
|
}),
|
|
92
|
-
),
|
|
86
|
+
).pipe(Effect.provideService(RedisServiceTag, redisManager)),
|
|
93
87
|
)
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
jobName: 'extract-memory',
|
|
100
|
-
concurrency: 10,
|
|
101
|
-
lockDuration: 900_000,
|
|
102
|
-
maxStalledCount: 10,
|
|
103
|
-
stalledInterval: 120_000,
|
|
104
|
-
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
105
|
-
prepare: () => getDeps().databaseService.connect(),
|
|
106
|
-
processor: processPostChatMemoryJob,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
export function enqueuePostChatMemory(job: PostChatMemoryExtractionJob, options?: { dedupeKey?: string }) {
|
|
110
|
-
return postChatMemory.enqueue(job, options?.dedupeKey ? { jobId: options.dedupeKey } : undefined)
|
|
90
|
+
export interface PostChatMemoryQueueRuntime {
|
|
91
|
+
enqueuePostChatMemory(job: PostChatMemoryExtractionJob, options?: { dedupeKey?: string }): Promise<void>
|
|
92
|
+
startWorker(options: { registerSignals?: boolean; deps: PostChatMemoryWorkerDeps }): WorkerHandle
|
|
111
93
|
}
|
|
112
94
|
|
|
113
|
-
|
|
114
|
-
registerSignals?: boolean
|
|
95
|
+
interface MakePostChatMemoryQueueRuntimeParams {
|
|
115
96
|
connectionProvider: () => IORedis
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
97
|
+
queueJobService: QueueJobService
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function makePostChatMemoryQueueRuntime(
|
|
101
|
+
params: MakePostChatMemoryQueueRuntimeParams,
|
|
102
|
+
): PostChatMemoryQueueRuntime {
|
|
103
|
+
const { connectionProvider, queueJobService } = params
|
|
104
|
+
const queue = createQueueFactoryWithDeps<PostChatMemoryExtractionJob, PostChatMemoryWorkerDeps>({
|
|
105
|
+
name: 'post-chat-memory',
|
|
106
|
+
displayName: 'Post-chat memory',
|
|
107
|
+
jobName: 'extract-memory',
|
|
108
|
+
concurrency: 10,
|
|
109
|
+
lockDuration: 900_000,
|
|
110
|
+
maxStalledCount: 10,
|
|
111
|
+
stalledInterval: 120_000,
|
|
112
|
+
defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
|
|
113
|
+
connectionProvider,
|
|
114
|
+
queueJobService,
|
|
115
|
+
prepare: ({ databaseService }) => Effect.runPromise(databaseService.connect()),
|
|
116
|
+
processor: processPostChatMemoryJob,
|
|
122
117
|
})
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
enqueuePostChatMemory: (job, options) =>
|
|
121
|
+
queue.enqueue(job, options?.dedupeKey ? { jobId: options.dedupeKey } : undefined),
|
|
122
|
+
startWorker: (options) =>
|
|
123
|
+
queue.startWorker({ deps: options.deps, registerSignals: options.registerSignals, connectionProvider }),
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
runStandaloneQueueWorker((runtime) => {
|
|
126
128
|
const resolve = <I, T>(tag: Context.Key<I, T>): T => runtime.runSync(Effect.service(tag))
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const redis = resolve(RedisServiceTag)
|
|
130
|
+
const postChatMemoryQueue = makePostChatMemoryQueueRuntime({
|
|
131
|
+
connectionProvider: () => redis.getConnectionForBullMQ(),
|
|
132
|
+
queueJobService: resolve(QueueJobServiceTag),
|
|
133
|
+
})
|
|
134
|
+
postChatMemoryQueue.startWorker({
|
|
135
|
+
deps: {
|
|
136
|
+
databaseService: resolve(DatabaseServiceTag),
|
|
137
|
+
memoryService: resolve(MemoryServiceTag),
|
|
138
|
+
redisManager: redis,
|
|
139
|
+
},
|
|
130
140
|
})
|
|
131
141
|
})
|