@lota-sdk/core 0.4.12 → 0.4.14
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 +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/live-turn-trace.ts +6 -49
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
|
@@ -6,7 +6,7 @@ import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
|
6
6
|
import type { RecordIdInput, RecordIdRef } from '../db/record-id'
|
|
7
7
|
import type { SurrealDBService } from '../db/service'
|
|
8
8
|
import { TABLES } from '../db/tables'
|
|
9
|
-
import { NotFoundError } from '../effect/errors'
|
|
9
|
+
import { ERROR_TAGS, NotFoundError } from '../effect/errors'
|
|
10
10
|
import { DatabaseServiceTag } from '../effect/services'
|
|
11
11
|
import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
|
|
12
12
|
|
|
@@ -17,10 +17,10 @@ interface BackgroundCursor {
|
|
|
17
17
|
id: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
class OrganizationServiceError extends Schema.TaggedErrorClass<OrganizationServiceError>()(
|
|
21
|
-
|
|
22
|
-
cause: Schema.Defect,
|
|
23
|
-
|
|
20
|
+
class OrganizationServiceError extends Schema.TaggedErrorClass<OrganizationServiceError>()(
|
|
21
|
+
ERROR_TAGS.OrganizationServiceError,
|
|
22
|
+
{ operation: Schema.String, cause: Schema.Defect },
|
|
23
|
+
) {}
|
|
24
24
|
|
|
25
25
|
function toOptionalCursorTimestamp(value: unknown): string | null {
|
|
26
26
|
return toOptionalIsoDateTimeString(value) ?? null
|
|
@@ -58,85 +58,80 @@ function toOrganizationServiceError(operation: string, cause: unknown): Organiza
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function makeOrganizationService(db: SurrealDBService) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Effect.
|
|
65
|
-
)
|
|
66
|
-
}
|
|
61
|
+
const createOrganization = Effect.fn('OrganizationService.create')(function* (params: { name: string }) {
|
|
62
|
+
const record = yield* db
|
|
63
|
+
.create(TABLES.ORGANIZATION, { name: params.name }, sdkOrganizationRecordSchema)
|
|
64
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('createOrganization', cause)))
|
|
65
|
+
return yield* toPublicEffect(record)
|
|
66
|
+
})
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const upsertOrganization = Effect.fn('OrganizationService.upsert')(function* (params: {
|
|
69
|
+
id: RecordIdInput
|
|
70
|
+
name: string
|
|
71
|
+
}) {
|
|
69
72
|
const organizationRef = ensureRecordId(params.id, TABLES.ORGANIZATION)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Effect.
|
|
73
|
-
)
|
|
74
|
-
}
|
|
73
|
+
const record = yield* db
|
|
74
|
+
.upsert(TABLES.ORGANIZATION, organizationRef, { name: params.name }, sdkOrganizationRecordSchema)
|
|
75
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('upsertOrganization', cause)))
|
|
76
|
+
return yield* toPublicEffect(record)
|
|
77
|
+
})
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
const listOrganizations = Effect.fn('OrganizationService.list')(function* () {
|
|
80
|
+
const records = yield* db
|
|
78
81
|
.findMany(TABLES.ORGANIZATION, {}, sdkOrganizationRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' })
|
|
79
|
-
.pipe(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
}
|
|
82
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('listOrganizations', cause)))
|
|
83
|
+
return yield* Effect.forEach(records, toPublicEffect)
|
|
84
|
+
})
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
const getOrganizationRecord = Effect.fn('OrganizationService.getRecord')(function* (organizationId: RecordIdRef) {
|
|
87
|
+
const record = yield* db
|
|
87
88
|
.findOne(
|
|
88
89
|
TABLES.ORGANIZATION,
|
|
89
90
|
{ id: ensureRecordId(organizationId, TABLES.ORGANIZATION) },
|
|
90
91
|
sdkOrganizationRecordSchema,
|
|
91
92
|
)
|
|
92
|
-
.pipe(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
),
|
|
97
|
-
)
|
|
98
|
-
}
|
|
93
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('getOrganizationRecord', cause)))
|
|
94
|
+
if (!record) return yield* organizationNotFoundError(organizationId)
|
|
95
|
+
return record
|
|
96
|
+
})
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
98
|
+
const getOrganization = Effect.fn('OrganizationService.get')(function* (organizationId: RecordIdInput) {
|
|
99
|
+
const record = yield* getOrganizationRecord(ensureRecordId(organizationId, TABLES.ORGANIZATION))
|
|
100
|
+
return yield* toPublicEffect(record)
|
|
101
|
+
})
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
const updateOrganization = Effect.fn('OrganizationService.update')(function* (
|
|
104
|
+
organizationId: RecordIdInput,
|
|
105
|
+
params: { name: string },
|
|
106
|
+
) {
|
|
107
|
+
const updated = yield* db
|
|
108
108
|
.update(
|
|
109
109
|
TABLES.ORGANIZATION,
|
|
110
110
|
ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
111
111
|
{ name: params.name },
|
|
112
112
|
sdkOrganizationRecordSchema,
|
|
113
113
|
)
|
|
114
|
-
.pipe(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
),
|
|
119
|
-
Effect.flatMap(toPublicEffect),
|
|
120
|
-
)
|
|
121
|
-
}
|
|
114
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('updateOrganization', cause)))
|
|
115
|
+
if (!updated) return yield* organizationNotFoundError(organizationId)
|
|
116
|
+
return yield* toPublicEffect(updated)
|
|
117
|
+
})
|
|
122
118
|
|
|
123
|
-
|
|
119
|
+
const deleteOrganization = Effect.fn('OrganizationService.delete')(function* (organizationId: RecordIdInput) {
|
|
124
120
|
const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return yield* organizationNotFoundError(organizationId)
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
}
|
|
121
|
+
yield* db
|
|
122
|
+
.deleteWhere(TABLES.ORGANIZATION_MEMBER, { out: organizationRef })
|
|
123
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('deleteOrganization.deleteMembers', cause)))
|
|
124
|
+
const deleted = yield* db
|
|
125
|
+
.deleteById(TABLES.ORGANIZATION, organizationRef)
|
|
126
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('deleteOrganization.deleteOrganization', cause)))
|
|
127
|
+
if (!deleted) return yield* organizationNotFoundError(organizationId)
|
|
128
|
+
})
|
|
137
129
|
|
|
138
|
-
|
|
139
|
-
|
|
130
|
+
const updateRegularChatDigestCursor = Effect.fn('OrganizationService.updateRegularChatDigestCursor')(function* (
|
|
131
|
+
organizationId: RecordIdRef,
|
|
132
|
+
cursor: BackgroundCursor,
|
|
133
|
+
) {
|
|
134
|
+
const updated = yield* db
|
|
140
135
|
.update(
|
|
141
136
|
TABLES.ORGANIZATION,
|
|
142
137
|
ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
@@ -146,25 +141,24 @@ export function makeOrganizationService(db: SurrealDBService) {
|
|
|
146
141
|
},
|
|
147
142
|
sdkOrganizationRecordSchema,
|
|
148
143
|
)
|
|
149
|
-
.pipe(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
}
|
|
144
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('updateRegularChatDigestCursor', cause)))
|
|
145
|
+
if (!updated) return yield* organizationNotFoundError(organizationId)
|
|
146
|
+
})
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
const updateSkillExtractionCursor = Effect.fn('OrganizationService.updateSkillExtractionCursor')(function* (
|
|
149
|
+
organizationId: RecordIdRef,
|
|
150
|
+
cursor: BackgroundCursor,
|
|
151
|
+
) {
|
|
152
|
+
const updated = yield* db
|
|
157
153
|
.update(
|
|
158
154
|
TABLES.ORGANIZATION,
|
|
159
155
|
ensureRecordId(organizationId, TABLES.ORGANIZATION),
|
|
160
156
|
{ skillExtractionLastCursorCreatedAt: cursor.createdAt, skillExtractionLastCursorId: cursor.id },
|
|
161
157
|
sdkOrganizationRecordSchema,
|
|
162
158
|
)
|
|
163
|
-
.pipe(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
167
|
-
}
|
|
159
|
+
.pipe(Effect.mapError((cause) => toOrganizationServiceError('updateSkillExtractionCursor', cause)))
|
|
160
|
+
if (!updated) return yield* organizationNotFoundError(organizationId)
|
|
161
|
+
})
|
|
168
162
|
|
|
169
163
|
return {
|
|
170
164
|
createOrganization,
|
|
@@ -23,7 +23,6 @@ import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
|
23
23
|
import type { SurrealDBService } from '../db/service'
|
|
24
24
|
import { TABLES } from '../db/tables'
|
|
25
25
|
import { BadRequestError, ConfigurationError, DatabaseError, ServiceError } from '../effect/errors'
|
|
26
|
-
import { isPromiseLike, makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
27
26
|
import { AgentConfigServiceTag, DatabaseServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
|
|
28
27
|
import type { PlanAgentHeartbeatQueueRuntime } from '../queues/plan-agent-heartbeat.queue'
|
|
29
28
|
import { LotaQueuesServiceTag } from '../queues/queues.service'
|
|
@@ -142,8 +141,6 @@ function toDispatchDatabaseError(message: string, cause: unknown) {
|
|
|
142
141
|
return new DatabaseError({ message, cause })
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
const tryDispatchPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
146
|
-
|
|
147
144
|
const matchDraftExecutor = (deps: OwnershipDispatcherDeps, node: { id: string; label: string }) =>
|
|
148
145
|
Match.type<PlanNodeOwner>().pipe(
|
|
149
146
|
Match.discriminator('executorType')('agent', (owner): PlanValidationIssueInput[] =>
|
|
@@ -181,23 +178,25 @@ const shouldAutoDispatchEffect = (deps: OwnershipDispatcherDeps, run: PlanRunRec
|
|
|
181
178
|
return true
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
const workspace = yield*
|
|
185
|
-
() => workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
|
|
186
|
-
'Failed to load workspace for dispatch eligibility.',
|
|
187
|
-
)
|
|
188
|
-
|
|
181
|
+
const workspace = yield* Effect.tryPromise({
|
|
182
|
+
try: () => workspaceProvider.getWorkspace(ensureRecordId(run.organizationId, TABLES.ORGANIZATION)),
|
|
183
|
+
catch: (cause) => new ServiceError({ message: 'Failed to load workspace for dispatch eligibility.', cause }),
|
|
184
|
+
})
|
|
185
|
+
const getLifecycleState = workspaceProvider.getLifecycleState
|
|
186
|
+
? (() => {
|
|
187
|
+
const run = workspaceProvider.getLifecycleState.bind(workspaceProvider)
|
|
188
|
+
return (workspaceRecord: Record<string, unknown>) => run(workspaceRecord)
|
|
189
|
+
})()
|
|
190
|
+
: undefined
|
|
191
|
+
if (!getLifecycleState) {
|
|
189
192
|
return true
|
|
190
193
|
}
|
|
191
194
|
|
|
192
|
-
const lifecycleState = yield* Effect.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return yield* tryDispatchPromise(() => result, 'Failed to read workspace lifecycle state.')
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return result
|
|
195
|
+
const lifecycleState = yield* Effect.tryPromise({
|
|
196
|
+
try: () => getLifecycleState(workspace),
|
|
197
|
+
catch: (cause) => new ServiceError({ message: 'Failed to read workspace lifecycle state.', cause }),
|
|
199
198
|
})
|
|
200
|
-
return lifecycleState
|
|
199
|
+
return lifecycleState.bootstrapActive !== true
|
|
201
200
|
})
|
|
202
201
|
|
|
203
202
|
const buildDispatchContextEffect = (
|
|
@@ -457,34 +456,36 @@ const dispatchRunToStableBoundaryEffect = (
|
|
|
457
456
|
schemaRegistry: spec.schemaRegistry,
|
|
458
457
|
})
|
|
459
458
|
|
|
460
|
-
yield*
|
|
461
|
-
(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
459
|
+
yield* deps.planExecutor
|
|
460
|
+
.submitNodeResult({
|
|
461
|
+
threadId: run.threadId,
|
|
462
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
463
|
+
nodeId: planNode.id,
|
|
464
|
+
emittedBy: planNode.owner.ref,
|
|
465
|
+
result,
|
|
466
|
+
})
|
|
467
|
+
.pipe(
|
|
468
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to submit plan node result.', cause })),
|
|
469
|
+
)
|
|
471
470
|
}),
|
|
472
471
|
)
|
|
473
472
|
|
|
474
473
|
if (Exit.isFailure(dispatchExit)) {
|
|
475
474
|
const failure = Cause.squash(dispatchExit.cause)
|
|
476
|
-
yield*
|
|
477
|
-
(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
475
|
+
yield* deps.planExecutor
|
|
476
|
+
.blockNodeOnDispatchFailure({
|
|
477
|
+
threadId: run.threadId,
|
|
478
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
479
|
+
nodeId: planNode.id,
|
|
480
|
+
emittedBy: planNode.owner.ref,
|
|
481
|
+
message: formatDispatchError(failure),
|
|
482
|
+
failureClass: classifyDispatchFailure({ ownerType: planNode.owner.executorType, error: failure }),
|
|
483
|
+
})
|
|
484
|
+
.pipe(
|
|
485
|
+
Effect.mapError(
|
|
486
|
+
(cause) => new ServiceError({ message: 'Failed to block plan node on dispatch failure.', cause }),
|
|
487
|
+
),
|
|
488
|
+
)
|
|
488
489
|
return yield* serializeRunEffect(deps, run.id)
|
|
489
490
|
}
|
|
490
491
|
}
|
|
@@ -4,7 +4,7 @@ import type { ResolvedAgentConfig } from '../../config/agent-defaults'
|
|
|
4
4
|
import { serverLogger } from '../../config/logger'
|
|
5
5
|
import { ensureRecordId } from '../../db/record-id'
|
|
6
6
|
import { TABLES } from '../../db/tables'
|
|
7
|
-
import {
|
|
7
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
8
8
|
import { AgentConfigServiceTag, RedisServiceTag } from '../../effect/services'
|
|
9
9
|
import type { PlanAgentHeartbeatQueueRuntime } from '../../queues/plan-agent-heartbeat.queue'
|
|
10
10
|
import { LotaQueuesServiceTag } from '../../queues/queues.service'
|
|
@@ -37,17 +37,10 @@ function buildWakeDedupeKey(params: {
|
|
|
37
37
|
return `${params.organizationId}:${params.threadId}:${params.runId}:${params.nodeId}:${params.agentId}`
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
class PlanAgentHeartbeatError extends Schema.TaggedErrorClass<PlanAgentHeartbeatError>()(
|
|
41
|
-
|
|
42
|
-
cause: Schema.Defect,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function tryHeartbeatPromise<A, R = never>(
|
|
46
|
-
operation: string,
|
|
47
|
-
thunk: () => PromiseLike<A> | Effect.Effect<A, unknown, R>,
|
|
48
|
-
): Effect.Effect<A, PlanAgentHeartbeatError, R> {
|
|
49
|
-
return effectTryPromise(thunk, (cause) => new PlanAgentHeartbeatError({ operation, cause }))
|
|
50
|
-
}
|
|
40
|
+
class PlanAgentHeartbeatError extends Schema.TaggedErrorClass<PlanAgentHeartbeatError>()(
|
|
41
|
+
ERROR_TAGS.PlanAgentHeartbeatError,
|
|
42
|
+
{ operation: Schema.String, cause: Schema.Defect },
|
|
43
|
+
) {}
|
|
51
44
|
|
|
52
45
|
function heartbeatServiceEffect<A, E, R = never>(
|
|
53
46
|
operation: string,
|
|
@@ -58,6 +51,7 @@ function heartbeatServiceEffect<A, E, R = never>(
|
|
|
58
51
|
|
|
59
52
|
interface PlanAgentHeartbeatDeps {
|
|
60
53
|
agentConfig: ResolvedAgentConfig
|
|
54
|
+
provideCurrentContext: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, never>
|
|
61
55
|
redis: RedisConnectionManager
|
|
62
56
|
planAgentQueryService: ReturnType<typeof makePlanAgentQueryService>
|
|
63
57
|
planExecutorService: ReturnType<typeof makePlanExecutorService>
|
|
@@ -69,6 +63,7 @@ interface PlanAgentHeartbeatDeps {
|
|
|
69
63
|
export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
70
64
|
const {
|
|
71
65
|
agentConfig,
|
|
66
|
+
provideCurrentContext,
|
|
72
67
|
planExecutorService,
|
|
73
68
|
planRunService,
|
|
74
69
|
redis,
|
|
@@ -84,7 +79,7 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
|
84
79
|
nodeId: string
|
|
85
80
|
agentId: string
|
|
86
81
|
reason: string
|
|
87
|
-
}): Effect.Effect<boolean, PlanAgentHeartbeatError,
|
|
82
|
+
}): Effect.Effect<boolean, PlanAgentHeartbeatError, never> =>
|
|
88
83
|
Effect.gen(function* () {
|
|
89
84
|
const threadRef = ensureRecordId(params.threadId, TABLES.THREAD)
|
|
90
85
|
yield* heartbeatServiceEffect(
|
|
@@ -148,27 +143,28 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
if (nodeRun.status === 'ready' && run.currentNodeId === params.nodeId) {
|
|
151
|
-
yield*
|
|
146
|
+
yield* heartbeatServiceEffect(
|
|
147
|
+
'transition-node-to-running',
|
|
152
148
|
planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId }),
|
|
153
149
|
)
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
const { triggerPlanNodeTurn } = yield*
|
|
157
|
-
'
|
|
158
|
-
() =>
|
|
159
|
-
)
|
|
152
|
+
const { triggerPlanNodeTurn } = yield* Effect.tryPromise({
|
|
153
|
+
try: () => import('../thread/thread-turn'),
|
|
154
|
+
catch: (cause) => new PlanAgentHeartbeatError({ operation: 'import-thread-turn', cause }),
|
|
155
|
+
})
|
|
160
156
|
|
|
161
|
-
yield*
|
|
162
|
-
|
|
157
|
+
yield* heartbeatServiceEffect(
|
|
158
|
+
'trigger-plan-node-turn',
|
|
159
|
+
provideCurrentContext(triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId })),
|
|
163
160
|
)
|
|
164
161
|
return true
|
|
165
162
|
}),
|
|
166
163
|
).pipe(Effect.mapError((cause) => new PlanAgentHeartbeatError({ operation: 'wake-node-lock', cause })))
|
|
167
164
|
})
|
|
168
165
|
|
|
169
|
-
const sweepEffect = (params?: { organizationId?: string }): Effect.Effect<void, PlanAgentHeartbeatError,
|
|
166
|
+
const sweepEffect = (params?: { organizationId?: string }): Effect.Effect<void, PlanAgentHeartbeatError, never> =>
|
|
170
167
|
Effect.gen(function* () {
|
|
171
|
-
const enqueuePlanAgentHeartbeatWake = planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake
|
|
172
168
|
const [actionable, recentlyUnblocked, approachingDeadlines] = yield* Effect.all([
|
|
173
169
|
heartbeatServiceEffect(
|
|
174
170
|
'get-actionable-nodes-for-agent',
|
|
@@ -213,7 +209,10 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
|
213
209
|
}
|
|
214
210
|
|
|
215
211
|
for (const target of wakeTargets.values()) {
|
|
216
|
-
yield*
|
|
212
|
+
yield* Effect.tryPromise({
|
|
213
|
+
try: () => planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake(target),
|
|
214
|
+
catch: (cause) => new PlanAgentHeartbeatError({ operation: 'enqueue-heartbeat-wake', cause }),
|
|
215
|
+
})
|
|
217
216
|
}
|
|
218
217
|
})
|
|
219
218
|
|
|
@@ -228,6 +227,9 @@ export class PlanAgentHeartbeatServiceTag extends Context.Service<
|
|
|
228
227
|
export const PlanAgentHeartbeatServiceLive = Layer.effect(
|
|
229
228
|
PlanAgentHeartbeatServiceTag,
|
|
230
229
|
Effect.gen(function* () {
|
|
230
|
+
const currentContext = yield* Effect.context()
|
|
231
|
+
const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
|
|
232
|
+
effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
|
|
231
233
|
const agentConfig = yield* AgentConfigServiceTag
|
|
232
234
|
const redis = yield* RedisServiceTag
|
|
233
235
|
const planAgentQueryService = yield* PlanAgentQueryServiceTag
|
|
@@ -237,6 +239,7 @@ export const PlanAgentHeartbeatServiceLive = Layer.effect(
|
|
|
237
239
|
const queues = yield* LotaQueuesServiceTag
|
|
238
240
|
return makePlanAgentHeartbeatService({
|
|
239
241
|
agentConfig,
|
|
242
|
+
provideCurrentContext,
|
|
240
243
|
redis,
|
|
241
244
|
planAgentQueryService,
|
|
242
245
|
planExecutorService: planExecutor,
|
|
@@ -8,7 +8,7 @@ import type { RecordIdInput } from '../../db/record-id'
|
|
|
8
8
|
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
9
9
|
import type { SurrealDBService } from '../../db/service'
|
|
10
10
|
import { TABLES } from '../../db/tables'
|
|
11
|
-
import {
|
|
11
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
12
12
|
import { AgentConfigServiceTag, DatabaseServiceTag } from '../../effect/services'
|
|
13
13
|
import { resolvePlanNodeExecutionVisibility } from '../../runtime/execution-plan-visibility'
|
|
14
14
|
import { nowDate, unsafeDateFrom } from '../../utils/date-time'
|
|
@@ -71,7 +71,7 @@ function isVisibleAgentNode(params: {
|
|
|
71
71
|
return { agentId: params.nodeSpec.owner.ref, visibility }
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
class PlanAgentQueryError extends Schema.TaggedErrorClass<PlanAgentQueryError>()(
|
|
74
|
+
class PlanAgentQueryError extends Schema.TaggedErrorClass<PlanAgentQueryError>()(ERROR_TAGS.PlanAgentQueryError, {
|
|
75
75
|
operation: Schema.String,
|
|
76
76
|
cause: Schema.Defect,
|
|
77
77
|
}) {}
|
|
@@ -80,20 +80,6 @@ function toPlanAgentQueryError(operation: string, cause: unknown): PlanAgentQuer
|
|
|
80
80
|
return new PlanAgentQueryError({ operation, cause })
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function queryEffect<A>(
|
|
84
|
-
operation: string,
|
|
85
|
-
thunk: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
86
|
-
): Effect.Effect<A, PlanAgentQueryError> {
|
|
87
|
-
return effectTryPromise(thunk, (cause) => toPlanAgentQueryError(operation, cause))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function queryServiceEffect<A, E>(
|
|
91
|
-
operation: string,
|
|
92
|
-
effect: Effect.Effect<A, E>,
|
|
93
|
-
): Effect.Effect<A, PlanAgentQueryError> {
|
|
94
|
-
return effect.pipe(Effect.mapError((cause) => toPlanAgentQueryError(operation, cause)))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
83
|
interface PlanAgentQueryDeps {
|
|
98
84
|
agentConfig: ResolvedAgentConfig
|
|
99
85
|
db: SurrealDBService
|
|
@@ -110,15 +96,15 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
|
|
|
110
96
|
}
|
|
111
97
|
|
|
112
98
|
const whereOrganization = organizationId ? ' AND organizationId = $organizationId' : ''
|
|
113
|
-
return
|
|
114
|
-
|
|
99
|
+
return db
|
|
100
|
+
.queryMany(
|
|
115
101
|
new BoundQuery(
|
|
116
102
|
`SELECT * FROM ${TABLES.PLAN_RUN} WHERE status INSIDE $statuses${whereOrganization} ORDER BY updatedAt DESC`,
|
|
117
103
|
bindings,
|
|
118
104
|
),
|
|
119
105
|
PlanRunSchema,
|
|
120
|
-
)
|
|
121
|
-
|
|
106
|
+
)
|
|
107
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-active-runs', cause)))
|
|
122
108
|
}
|
|
123
109
|
|
|
124
110
|
const getActionableNodesForAgentEffect = (params: {
|
|
@@ -130,12 +116,18 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
|
|
|
130
116
|
const actionable: ActionablePlanAgentNode[] = []
|
|
131
117
|
|
|
132
118
|
for (const run of runs) {
|
|
133
|
-
const spec = yield*
|
|
119
|
+
const spec = yield* planRunService
|
|
120
|
+
.getPlanSpecById(run.planSpecId)
|
|
121
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('get-plan-spec-by-id', cause)))
|
|
134
122
|
|
|
135
123
|
if (spec.executionMode === 'graph-full') {
|
|
136
124
|
const [nodeSpecs, nodeRuns] = yield* Effect.all([
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
planRunService
|
|
126
|
+
.listNodeSpecs(spec.id)
|
|
127
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-node-specs', cause))),
|
|
128
|
+
planRunService
|
|
129
|
+
.listNodeRuns(run.id)
|
|
130
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-node-runs', cause))),
|
|
139
131
|
])
|
|
140
132
|
for (const nodeRun of nodeRuns) {
|
|
141
133
|
if (!ACTIONABLE_NODE_STATUSES.has(nodeRun.status)) continue
|
|
@@ -163,8 +155,12 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
|
|
|
163
155
|
}
|
|
164
156
|
|
|
165
157
|
const [nodeSpec, nodeRun] = yield* Effect.all([
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
planRunService
|
|
159
|
+
.getNodeSpecByNodeId(spec.id, currentNodeId)
|
|
160
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('get-node-spec-by-node-id', cause))),
|
|
161
|
+
planRunService
|
|
162
|
+
.getNodeRunByNodeId(run.id, currentNodeId)
|
|
163
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('get-node-run-by-node-id', cause))),
|
|
168
164
|
])
|
|
169
165
|
if (!ACTIONABLE_NODE_STATUSES.has(nodeRun.status)) {
|
|
170
166
|
continue
|
|
@@ -202,10 +198,16 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
|
|
|
202
198
|
const matches: ApproachingDeadlineNode[] = []
|
|
203
199
|
|
|
204
200
|
for (const run of runs) {
|
|
205
|
-
const spec = yield*
|
|
201
|
+
const spec = yield* planRunService
|
|
202
|
+
.getPlanSpecById(run.planSpecId)
|
|
203
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('get-plan-spec-by-id', cause)))
|
|
206
204
|
const [nodeSpecs, nodeRuns] = yield* Effect.all([
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
planRunService
|
|
206
|
+
.listNodeSpecs(spec.id)
|
|
207
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-node-specs', cause))),
|
|
208
|
+
planRunService
|
|
209
|
+
.listNodeRuns(run.id)
|
|
210
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-node-runs', cause))),
|
|
209
211
|
])
|
|
210
212
|
const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
|
|
211
213
|
|
|
@@ -257,10 +259,16 @@ export function makePlanAgentQueryService(deps: PlanAgentQueryDeps) {
|
|
|
257
259
|
const matches: RecentlyUnblockedNode[] = []
|
|
258
260
|
|
|
259
261
|
for (const run of runs) {
|
|
260
|
-
const spec = yield*
|
|
262
|
+
const spec = yield* planRunService
|
|
263
|
+
.getPlanSpecById(run.planSpecId)
|
|
264
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('get-plan-spec-by-id', cause)))
|
|
261
265
|
const [nodeSpecs, events] = yield* Effect.all([
|
|
262
|
-
|
|
263
|
-
|
|
266
|
+
planRunService
|
|
267
|
+
.listNodeSpecs(spec.id)
|
|
268
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-node-specs', cause))),
|
|
269
|
+
planRunService
|
|
270
|
+
.listEvents(run.id, 200)
|
|
271
|
+
.pipe(Effect.mapError((cause) => toPlanAgentQueryError('list-events', cause))),
|
|
264
272
|
])
|
|
265
273
|
const nodeSpecsById = new Map(nodeSpecs.map((nodeSpec) => [nodeSpec.nodeId, nodeSpec]))
|
|
266
274
|
|
|
@@ -6,7 +6,6 @@ import { aiLogger } from '../../config/logger'
|
|
|
6
6
|
import { ensureRecordId } from '../../db/record-id'
|
|
7
7
|
import type { SurrealDBService } from '../../db/service'
|
|
8
8
|
import { TABLES } from '../../db/tables'
|
|
9
|
-
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
10
9
|
import { nowEpochMillis, unsafeDateFrom } from '../../utils/date-time'
|
|
11
10
|
import type { makeFeedbackLoopService } from '../feedback-loop.service'
|
|
12
11
|
import type { makeInstitutionalMemoryService } from '../institutional-memory.service'
|
|
@@ -29,21 +28,10 @@ interface PlanCompletionSideEffectsDeps {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
class PlanCompletionSideEffectsError extends Schema.TaggedErrorClass<PlanCompletionSideEffectsError>()(
|
|
32
|
-
'PlanCompletionSideEffectsError',
|
|
31
|
+
'@lota-sdk/core/PlanCompletionSideEffectsError',
|
|
33
32
|
{ message: Schema.String, cause: Schema.Defect },
|
|
34
33
|
) {}
|
|
35
34
|
|
|
36
|
-
const effectTryPlanCompletionPromise = makeEffectTryPromiseWithMessage(
|
|
37
|
-
(message, cause) => new PlanCompletionSideEffectsError({ message, cause }),
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
function tryPlanCompletionPromise<A>(
|
|
41
|
-
message: string,
|
|
42
|
-
evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
43
|
-
): Effect.Effect<A, PlanCompletionSideEffectsError> {
|
|
44
|
-
return effectTryPlanCompletionPromise(evaluate, message)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
35
|
export function makePlanCompletionSideEffects({
|
|
48
36
|
databaseService,
|
|
49
37
|
feedbackLoopService,
|
|
@@ -107,8 +95,8 @@ export function makePlanCompletionSideEffects({
|
|
|
107
95
|
if (recommendations.length > 0) {
|
|
108
96
|
const run = yield* planRunService.getRunById(params.runId)
|
|
109
97
|
const specRecord = yield* planRunService.getPlanSpecById(run.planSpecId)
|
|
110
|
-
const event = yield*
|
|
111
|
-
|
|
98
|
+
const event = yield* databaseService
|
|
99
|
+
.create(
|
|
112
100
|
TABLES.PLAN_EVENT,
|
|
113
101
|
{
|
|
114
102
|
planSpecId: ensureRecordId(specRecord.id, TABLES.PLAN_SPEC),
|
|
@@ -119,8 +107,16 @@ export function makePlanCompletionSideEffects({
|
|
|
119
107
|
emittedBy: 'system',
|
|
120
108
|
},
|
|
121
109
|
PlanEventSchema,
|
|
122
|
-
)
|
|
123
|
-
|
|
110
|
+
)
|
|
111
|
+
.pipe(
|
|
112
|
+
Effect.mapError(
|
|
113
|
+
(cause) =>
|
|
114
|
+
new PlanCompletionSideEffectsError({
|
|
115
|
+
message: 'Failed to create feedback analyzed plan event.',
|
|
116
|
+
cause,
|
|
117
|
+
}),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
124
120
|
yield* planEventDeliveryService
|
|
125
121
|
.dispatchEvent(event)
|
|
126
122
|
.pipe(
|
|
@@ -4,6 +4,7 @@ import { Context, Schema, Effect, Layer } from 'effect'
|
|
|
4
4
|
import { serverLogger } from '../../config/logger'
|
|
5
5
|
import { recordIdToString } from '../../db/record-id'
|
|
6
6
|
import { TABLES } from '../../db/tables'
|
|
7
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
7
8
|
import { nowEpochMillis } from '../../utils/date-time'
|
|
8
9
|
import type { makePlanRunService } from './plan-run.service'
|
|
9
10
|
import { PlanRunServiceTag } from './plan-run.service'
|
|
@@ -15,7 +16,7 @@ export interface DependencyResolutionResult {
|
|
|
15
16
|
notifications: Array<{ dependency: PlanDependency; reason: string }>
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
class PlanCoordinationError extends Schema.TaggedErrorClass<PlanCoordinationError>()(
|
|
19
|
+
class PlanCoordinationError extends Schema.TaggedErrorClass<PlanCoordinationError>()(ERROR_TAGS.PlanCoordinationError, {
|
|
19
20
|
message: Schema.String,
|
|
20
21
|
cause: Schema.optional(Schema.Defect),
|
|
21
22
|
}) {}
|