@lota-sdk/core 0.4.10 → 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.
Files changed (108) hide show
  1. package/package.json +2 -2
  2. package/src/ai-gateway/ai-gateway.ts +149 -95
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/thread-defaults.ts +1 -18
  7. package/src/create-runtime.ts +90 -28
  8. package/src/db/base.service.ts +30 -38
  9. package/src/db/service.ts +489 -545
  10. package/src/effect/index.ts +0 -2
  11. package/src/effect/layers.ts +6 -13
  12. package/src/embeddings/provider.ts +2 -7
  13. package/src/index.ts +4 -5
  14. package/src/queues/autonomous-job.queue.ts +159 -113
  15. package/src/queues/context-compaction.queue.ts +39 -25
  16. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  17. package/src/queues/document-processor.queue.ts +5 -3
  18. package/src/queues/index.ts +1 -0
  19. package/src/queues/memory-consolidation.queue.ts +79 -53
  20. package/src/queues/organization-learning.queue.ts +63 -39
  21. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  22. package/src/queues/plan-scheduler.queue.ts +100 -84
  23. package/src/queues/post-chat-memory.queue.ts +55 -33
  24. package/src/queues/queue-factory.ts +40 -41
  25. package/src/queues/queues.service.ts +61 -0
  26. package/src/queues/title-generation.queue.ts +42 -31
  27. package/src/redis/org-memory-lock.ts +24 -9
  28. package/src/redis/redis-lease-lock.ts +8 -1
  29. package/src/runtime/agent-identity-overrides.ts +7 -3
  30. package/src/runtime/agent-runtime-policy.ts +9 -4
  31. package/src/runtime/agent-stream-helpers.ts +9 -4
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  33. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  34. package/src/runtime/domain-layer.ts +15 -4
  35. package/src/runtime/execution-plan-visibility.ts +5 -2
  36. package/src/runtime/graph-designer.ts +0 -22
  37. package/src/runtime/index.ts +1 -0
  38. package/src/runtime/indexed-repositories-policy.ts +2 -6
  39. package/src/runtime/plugin-resolution.ts +29 -12
  40. package/src/runtime/post-turn-side-effects.ts +139 -141
  41. package/src/runtime/runtime-config.ts +0 -6
  42. package/src/runtime/runtime-extensions.ts +0 -54
  43. package/src/runtime/runtime-lifecycle.ts +4 -4
  44. package/src/runtime/runtime-services.ts +122 -53
  45. package/src/runtime/runtime-worker-registry.ts +113 -30
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  47. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  48. package/src/runtime/social-chat/social-chat.ts +35 -20
  49. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  50. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  51. package/src/runtime/thread-chat-helpers.ts +18 -9
  52. package/src/runtime/thread-turn-context.ts +7 -47
  53. package/src/runtime/turn-lifecycle.ts +6 -14
  54. package/src/services/agent-activity.service.ts +168 -175
  55. package/src/services/agent-executor.service.ts +35 -16
  56. package/src/services/attachment.service.ts +4 -70
  57. package/src/services/autonomous-job.service.ts +53 -61
  58. package/src/services/context-compaction.service.ts +7 -9
  59. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  60. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  61. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  62. package/src/services/global-orchestrator.service.ts +18 -7
  63. package/src/services/graph-full-routing.ts +7 -6
  64. package/src/services/memory/memory-conversation.ts +10 -5
  65. package/src/services/memory/memory.service.ts +11 -8
  66. package/src/services/ownership-dispatcher.service.ts +16 -5
  67. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  68. package/src/services/plan/plan-agent-query.service.ts +12 -8
  69. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  70. package/src/services/plan/plan-cycle.service.ts +7 -45
  71. package/src/services/plan/plan-deadline.service.ts +28 -17
  72. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  73. package/src/services/plan/plan-executor-context.ts +2 -0
  74. package/src/services/plan/plan-executor-graph.ts +366 -391
  75. package/src/services/plan/plan-executor.service.ts +13 -91
  76. package/src/services/plan/plan-scheduler.service.ts +62 -49
  77. package/src/services/plan/plan-transaction-events.ts +1 -1
  78. package/src/services/recent-activity-title.service.ts +6 -2
  79. package/src/services/thread/thread-bootstrap.ts +11 -9
  80. package/src/services/thread/thread-message.service.ts +6 -5
  81. package/src/services/thread/thread-turn-execution.ts +86 -82
  82. package/src/services/thread/thread-turn-preparation.service.ts +47 -24
  83. package/src/services/thread/thread-turn-streaming.ts +20 -25
  84. package/src/services/thread/thread-turn.ts +25 -44
  85. package/src/services/thread/thread.service.ts +21 -6
  86. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  87. package/src/system-agents/thread-router.agent.ts +23 -20
  88. package/src/tools/execution-plan.tool.ts +8 -3
  89. package/src/tools/fetch-webpage.tool.ts +10 -9
  90. package/src/tools/firecrawl-client.ts +0 -15
  91. package/src/tools/remember-memory.tool.ts +3 -6
  92. package/src/tools/research-topic.tool.ts +12 -3
  93. package/src/tools/search-web.tool.ts +10 -9
  94. package/src/tools/search.tool.ts +4 -5
  95. package/src/tools/team-think.tool.ts +139 -121
  96. package/src/workers/bootstrap.ts +9 -10
  97. package/src/workers/memory-consolidation.worker.ts +4 -1
  98. package/src/workers/organization-learning.worker.ts +15 -2
  99. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  100. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  101. package/src/workers/skill-extraction.runner.ts +13 -15
  102. package/src/workers/worker-utils.ts +6 -18
  103. package/src/effect/awaitable-effect.ts +0 -96
  104. package/src/effect/runtime-ref.ts +0 -25
  105. package/src/effect/runtime.ts +0 -46
  106. package/src/redis/runtime-connection.ts +0 -20
  107. package/src/runtime/runtime-accessors.ts +0 -92
  108. package/src/runtime/runtime-token.ts +0 -47
@@ -9,9 +9,9 @@ import type {
9
9
  } from '@lota-sdk/shared'
10
10
  import { Context, Schema, Effect, Layer } from 'effect'
11
11
 
12
- import { getAgentRoster } from '../config/agent-defaults'
12
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
13
13
  import { serverLogger } from '../config/logger'
14
- import { toAwaitableEffect } from '../effect/awaitable-effect'
14
+ import { AgentConfigServiceTag } from '../effect/services'
15
15
  import { unsafeDateFrom } from '../utils/date-time'
16
16
  import type { makeExecutionPlanService } from './execution-plan/execution-plan.service'
17
17
  import { ExecutionPlanServiceTag } from './execution-plan/execution-plan.service'
@@ -37,6 +37,7 @@ type ActivePlanEntry = {
37
37
  }
38
38
 
39
39
  type AgentActivityDeps = {
40
+ agentConfig: ResolvedAgentConfig
40
41
  executionPlanService: Pick<ReturnType<typeof makeExecutionPlanService>, 'getActivePlansForThread'>
41
42
  threadService: Pick<ReturnType<typeof makeThreadService>, 'listThreads'>
42
43
  }
@@ -154,158 +155,97 @@ function buildPlanViewNode(
154
155
  }
155
156
 
156
157
  export function getBoard(userRef: string, orgRef: string, deps: AgentActivityDeps) {
157
- return toAwaitableEffect(
158
- Effect.gen(function* () {
159
- const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
160
- const cards = activePlans
161
- .filter(({ plan }) => !isPendingPlanApproval(plan))
162
- .flatMap(({ plan, thread }) => plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)))
163
-
164
- const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
165
- status,
166
- label: COLUMN_LABELS[status],
167
- nodes: cards.filter((card) => card.status === status),
168
- }))
169
-
170
- return {
171
- columns,
172
- summary: {
173
- totalNodes: cards.length,
174
- completedNodes: cards.filter((card) => card.status === 'completed').length,
175
- activePlanCount: activePlans.length,
176
- pendingApprovalCount:
177
- countPendingPlanApprovals(activePlans) + cards.filter((card) => card.hasApproval).length,
178
- },
179
- }
180
- }),
181
- )
158
+ return Effect.gen(function* () {
159
+ const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
160
+ const cards = activePlans
161
+ .filter(({ plan }) => !isPendingPlanApproval(plan))
162
+ .flatMap(({ plan, thread }) => plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)))
163
+
164
+ const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
165
+ status,
166
+ label: COLUMN_LABELS[status],
167
+ nodes: cards.filter((card) => card.status === status),
168
+ }))
169
+
170
+ return {
171
+ columns,
172
+ summary: {
173
+ totalNodes: cards.length,
174
+ completedNodes: cards.filter((card) => card.status === 'completed').length,
175
+ activePlanCount: activePlans.length,
176
+ pendingApprovalCount: countPendingPlanApprovals(activePlans) + cards.filter((card) => card.hasApproval).length,
177
+ },
178
+ }
179
+ })
182
180
  }
183
181
 
184
182
  export function getPlanView(orgRef: string, planRunId: string, userRef: string, deps: AgentActivityDeps) {
185
- return toAwaitableEffect(
186
- Effect.gen(function* () {
187
- const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
188
- const match = activePlans.find(({ plan }) => plan.runId === planRunId)
189
- if (!match) return null
190
-
191
- const { plan, thread } = match
192
- return {
193
- planRunId: plan.runId,
194
- title: plan.title,
195
- objective: plan.objective,
196
- status: plan.status,
197
- leadAgentId: plan.leadAgentId,
198
- progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
199
- nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
200
- edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
201
- }
202
- }),
203
- )
183
+ return Effect.gen(function* () {
184
+ const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
185
+ const match = activePlans.find(({ plan }) => plan.runId === planRunId)
186
+ if (!match) return null
187
+
188
+ const { plan, thread } = match
189
+ return {
190
+ planRunId: plan.runId,
191
+ title: plan.title,
192
+ objective: plan.objective,
193
+ status: plan.status,
194
+ leadAgentId: plan.leadAgentId,
195
+ progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
196
+ nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
197
+ edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
198
+ }
199
+ })
204
200
  }
205
201
 
206
202
  export function getMyTasks(userRef: string, orgRef: string, deps: AgentActivityDeps) {
207
- return toAwaitableEffect(
208
- Effect.gen(function* () {
209
- const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
210
- const tasks: PlanNodeCard[] = []
211
- const pendingPlanApprovalCount = countPendingPlanApprovals(activePlans)
212
-
213
- for (const { plan, thread } of activePlans) {
214
- if (isPendingPlanApproval(plan)) {
215
- continue
216
- }
217
-
218
- for (const node of plan.nodes) {
219
- const humanOwned = node.owner.executorType === 'user'
220
- const awaitingHuman = node.status === 'awaiting-human'
221
- if (!humanOwned && !awaitingHuman) continue
203
+ return Effect.gen(function* () {
204
+ const activePlans = yield* getAllActivePlans(userRef, orgRef, deps)
205
+ const tasks: PlanNodeCard[] = []
206
+ const pendingPlanApprovalCount = countPendingPlanApprovals(activePlans)
222
207
 
223
- tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
224
- }
208
+ for (const { plan, thread } of activePlans) {
209
+ if (isPendingPlanApproval(plan)) {
210
+ continue
225
211
  }
226
212
 
227
- return { tasks, pendingApprovalCount: pendingPlanApprovalCount + tasks.filter((task) => task.hasApproval).length }
228
- }),
229
- )
230
- }
213
+ for (const node of plan.nodes) {
214
+ const humanOwned = node.owner.executorType === 'user'
215
+ const awaitingHuman = node.status === 'awaiting-human'
216
+ if (!humanOwned && !awaitingHuman) continue
231
217
 
232
- export function getAgentActivity(userRef: string, orgRef: string, deps: AgentActivityDeps) {
233
- return toAwaitableEffect(
234
- Effect.gen(function* () {
235
- const [activePlans, userTasks] = yield* Effect.all([
236
- getAllActivePlans(userRef, orgRef, deps),
237
- getMyTasks(userRef, orgRef, deps),
238
- ])
239
- const activityByAgent = new Map<string, AgentActivityEntry>()
240
-
241
- const agentRoster = getAgentRoster()
242
- for (const agentId of agentRoster) {
243
- activityByAgent.set(agentId, {
244
- agentId,
245
- counts: createEmptyCounts(),
246
- tasks: [],
247
- projects: [],
248
- isLeadingActivePlan: false,
249
- isRunning: false,
250
- lastActiveAt: null,
251
- })
218
+ tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
252
219
  }
220
+ }
253
221
 
254
- for (const { plan, thread } of activePlans) {
255
- if (isPendingPlanApproval(plan)) {
256
- if (plan.leadAgentId.trim()) {
257
- const leadEntry = activityByAgent.get(plan.leadAgentId) ?? {
258
- agentId: plan.leadAgentId,
259
- counts: createEmptyCounts(),
260
- tasks: [],
261
- projects: [],
262
- isLeadingActivePlan: false,
263
- isRunning: false,
264
- lastActiveAt: null,
265
- }
266
- activityByAgent.set(plan.leadAgentId, leadEntry)
267
- leadEntry.isLeadingActivePlan = true
268
- if (!leadEntry.projects.some((project) => project.planRunId === plan.runId)) {
269
- leadEntry.projects.push({
270
- threadId: thread.id,
271
- threadTitle: thread.title,
272
- planRunId: plan.runId,
273
- planTitle: plan.title,
274
- status: plan.status,
275
- })
276
- }
277
- leadEntry.isRunning = leadEntry.isRunning || thread.isRunning
278
- leadEntry.lastActiveAt = maxIsoDate(leadEntry.lastActiveAt, thread.updatedAt)
279
- }
280
- continue
281
- }
222
+ return { tasks, pendingApprovalCount: pendingPlanApprovalCount + tasks.filter((task) => task.hasApproval).length }
223
+ })
224
+ }
282
225
 
283
- const involvedAgents = new Set<string>()
284
-
285
- for (const node of plan.nodes) {
286
- if (node.owner.executorType !== 'agent') continue
287
-
288
- const agentId = node.owner.ref
289
- const entry =
290
- activityByAgent.get(agentId) ??
291
- ({
292
- agentId,
293
- counts: createEmptyCounts(),
294
- tasks: [],
295
- projects: [],
296
- isLeadingActivePlan: false,
297
- isRunning: false,
298
- lastActiveAt: null,
299
- } as AgentActivityEntry)
300
- activityByAgent.set(agentId, entry)
301
- involvedAgents.add(agentId)
302
- incrementCounts(entry.counts, node.status)
303
-
304
- if (!isCompletedStatus(node.status)) {
305
- entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
306
- }
307
- }
226
+ export function getAgentActivity(userRef: string, orgRef: string, deps: AgentActivityDeps) {
227
+ return Effect.gen(function* () {
228
+ const [activePlans, userTasks] = yield* Effect.all([
229
+ getAllActivePlans(userRef, orgRef, deps),
230
+ getMyTasks(userRef, orgRef, deps),
231
+ ])
232
+ const activityByAgent = new Map<string, AgentActivityEntry>()
233
+
234
+ const agentRoster = deps.agentConfig.roster
235
+ for (const agentId of agentRoster) {
236
+ activityByAgent.set(agentId, {
237
+ agentId,
238
+ counts: createEmptyCounts(),
239
+ tasks: [],
240
+ projects: [],
241
+ isLeadingActivePlan: false,
242
+ isRunning: false,
243
+ lastActiveAt: null,
244
+ })
245
+ }
308
246
 
247
+ for (const { plan, thread } of activePlans) {
248
+ if (isPendingPlanApproval(plan)) {
309
249
  if (plan.leadAgentId.trim()) {
310
250
  const leadEntry = activityByAgent.get(plan.leadAgentId) ?? {
311
251
  agentId: plan.leadAgentId,
@@ -318,15 +258,8 @@ export function getAgentActivity(userRef: string, orgRef: string, deps: AgentAct
318
258
  }
319
259
  activityByAgent.set(plan.leadAgentId, leadEntry)
320
260
  leadEntry.isLeadingActivePlan = true
321
- involvedAgents.add(plan.leadAgentId)
322
- }
323
-
324
- for (const agentId of involvedAgents) {
325
- const entry = activityByAgent.get(agentId)
326
- if (!entry) continue
327
-
328
- if (!entry.projects.some((project) => project.planRunId === plan.runId)) {
329
- entry.projects.push({
261
+ if (!leadEntry.projects.some((project) => project.planRunId === plan.runId)) {
262
+ leadEntry.projects.push({
330
263
  threadId: thread.id,
331
264
  threadTitle: thread.title,
332
265
  planRunId: plan.runId,
@@ -334,38 +267,97 @@ export function getAgentActivity(userRef: string, orgRef: string, deps: AgentAct
334
267
  status: plan.status,
335
268
  })
336
269
  }
270
+ leadEntry.isRunning = leadEntry.isRunning || thread.isRunning
271
+ leadEntry.lastActiveAt = maxIsoDate(leadEntry.lastActiveAt, thread.updatedAt)
272
+ }
273
+ continue
274
+ }
275
+
276
+ const involvedAgents = new Set<string>()
277
+
278
+ for (const node of plan.nodes) {
279
+ if (node.owner.executorType !== 'agent') continue
280
+
281
+ const agentId = node.owner.ref
282
+ const entry =
283
+ activityByAgent.get(agentId) ??
284
+ ({
285
+ agentId,
286
+ counts: createEmptyCounts(),
287
+ tasks: [],
288
+ projects: [],
289
+ isLeadingActivePlan: false,
290
+ isRunning: false,
291
+ lastActiveAt: null,
292
+ } as AgentActivityEntry)
293
+ activityByAgent.set(agentId, entry)
294
+ involvedAgents.add(agentId)
295
+ incrementCounts(entry.counts, node.status)
337
296
 
338
- entry.isRunning = entry.isRunning || thread.isRunning
339
- entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
297
+ if (!isCompletedStatus(node.status)) {
298
+ entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
340
299
  }
341
300
  }
342
301
 
343
- const agents = [...activityByAgent.values()].sort((left, right) => {
344
- const leftIndex = agentRoster.indexOf(left.agentId)
345
- const rightIndex = agentRoster.indexOf(right.agentId)
346
- if (leftIndex !== -1 || rightIndex !== -1) {
347
- if (leftIndex === -1) return 1
348
- if (rightIndex === -1) return -1
349
- return leftIndex - rightIndex
302
+ if (plan.leadAgentId.trim()) {
303
+ const leadEntry = activityByAgent.get(plan.leadAgentId) ?? {
304
+ agentId: plan.leadAgentId,
305
+ counts: createEmptyCounts(),
306
+ tasks: [],
307
+ projects: [],
308
+ isLeadingActivePlan: false,
309
+ isRunning: false,
310
+ lastActiveAt: null,
350
311
  }
351
- return left.agentId.localeCompare(right.agentId)
352
- })
312
+ activityByAgent.set(plan.leadAgentId, leadEntry)
313
+ leadEntry.isLeadingActivePlan = true
314
+ involvedAgents.add(plan.leadAgentId)
315
+ }
353
316
 
354
- return {
355
- agents,
356
- userActivity: {
357
- taskCount: userTasks.tasks.length,
358
- pendingApprovalCount: userTasks.pendingApprovalCount,
359
- awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
360
- lastActiveAt: activePlans.reduce<string | null>(
361
- (latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
362
- null,
363
- ),
364
- },
365
- totalActivePlans: activePlans.length,
317
+ for (const agentId of involvedAgents) {
318
+ const entry = activityByAgent.get(agentId)
319
+ if (!entry) continue
320
+
321
+ if (!entry.projects.some((project) => project.planRunId === plan.runId)) {
322
+ entry.projects.push({
323
+ threadId: thread.id,
324
+ threadTitle: thread.title,
325
+ planRunId: plan.runId,
326
+ planTitle: plan.title,
327
+ status: plan.status,
328
+ })
329
+ }
330
+
331
+ entry.isRunning = entry.isRunning || thread.isRunning
332
+ entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
333
+ }
334
+ }
335
+
336
+ const agents = [...activityByAgent.values()].sort((left, right) => {
337
+ const leftIndex = agentRoster.indexOf(left.agentId)
338
+ const rightIndex = agentRoster.indexOf(right.agentId)
339
+ if (leftIndex !== -1 || rightIndex !== -1) {
340
+ if (leftIndex === -1) return 1
341
+ if (rightIndex === -1) return -1
342
+ return leftIndex - rightIndex
366
343
  }
367
- }),
368
- )
344
+ return left.agentId.localeCompare(right.agentId)
345
+ })
346
+
347
+ return {
348
+ agents,
349
+ userActivity: {
350
+ taskCount: userTasks.tasks.length,
351
+ pendingApprovalCount: userTasks.pendingApprovalCount,
352
+ awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
353
+ lastActiveAt: activePlans.reduce<string | null>(
354
+ (latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
355
+ null,
356
+ ),
357
+ },
358
+ totalActivePlans: activePlans.length,
359
+ }
360
+ })
369
361
  }
370
362
 
371
363
  export function getAllActivePlans(userRef: string, orgRef: string, deps: AgentActivityDeps) {
@@ -452,8 +444,9 @@ export class AgentActivityServiceTag extends Context.Service<AgentActivityServic
452
444
  export const AgentActivityServiceLive = Layer.effect(
453
445
  AgentActivityServiceTag,
454
446
  Effect.gen(function* () {
447
+ const agentConfig = yield* AgentConfigServiceTag
455
448
  const executionPlanService = yield* ExecutionPlanServiceTag
456
449
  const threadService = yield* ThreadServiceTag
457
- return createAgentActivityService({ executionPlanService, threadService })
450
+ return createAgentActivityService({ agentConfig, executionPlanService, threadService })
458
451
  }),
459
452
  )
@@ -12,14 +12,19 @@ import { stepCountIs, tool } from 'ai'
12
12
  import type { ToolSet } from 'ai'
13
13
  import { Context, Schema, Effect, Layer } from 'effect'
14
14
 
15
- import { getAgentRoster, getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
15
+ import type { ResolvedAgentConfig, ResolvedAgentFactoryConfig } from '../config/agent-defaults'
16
16
  import { ensureRecordId } from '../db/record-id'
17
17
  import type { SurrealDBService } from '../db/service'
18
18
  import { TABLES } from '../db/tables'
19
19
  import { BadRequestError, NotFoundError } from '../effect/errors'
20
20
  import { makeEffectTryPromiseWithOperation } from '../effect/helpers'
21
- import { runPromise } from '../effect/runtime'
22
- import { DatabaseServiceTag } from '../effect/services'
21
+ import {
22
+ AgentConfigServiceTag,
23
+ AgentFactoryServiceTag,
24
+ DatabaseServiceTag,
25
+ RuntimeAdaptersServiceTag,
26
+ TurnHooksServiceTag,
27
+ } from '../effect/services'
23
28
  import { toValidationError } from '../effect/zod'
24
29
  import {
25
30
  OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES,
@@ -33,7 +38,6 @@ import {
33
38
  getGithubInstallationForOrganization,
34
39
  getLinearInstallationByOrgId,
35
40
  } from '../runtime/plugin-resolution'
36
- import { getTurnHooks } from '../runtime/runtime-extensions'
37
41
  import { readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
38
42
  import { nowDate } from '../utils/date-time'
39
43
  import type { makeNodeWorkspaceService } from './node-workspace.service'
@@ -91,8 +95,8 @@ export function buildWriteIntentDispatchPrompt(nodeSpec: PlanNodeSpec): string {
91
95
 
92
96
  const MAX_SELF_CORRECTION_RETRIES = 3
93
97
 
94
- function validateOwner(agentId: string, nodeId: string): PlanValidationIssueInput[] {
95
- if (!getAgentRoster().includes(agentId)) {
98
+ function validateOwner(agentConfig: ResolvedAgentConfig, agentId: string, nodeId: string): PlanValidationIssueInput[] {
99
+ if (!agentConfig.roster.includes(agentId)) {
96
100
  return [
97
101
  {
98
102
  severity: 'blocking',
@@ -140,7 +144,7 @@ function buildWriteIntentTool(params: {
140
144
  'Write a validated artifact or structured output field. Call this for each deliverable. If validation fails, correct your payload and try again.',
141
145
  inputSchema: WriteIntentSchema,
142
146
  execute: (intent: WriteIntent) =>
143
- runPromise(
147
+ Effect.runPromise(
144
148
  Effect.gen(function* () {
145
149
  const correctionCount = params.workspace.sys.correctionCounts.get(intent.targetPath) ?? 0
146
150
 
@@ -193,7 +197,7 @@ function createExecuteNode(deps: AgentExecutorDeps) {
193
197
  }
194
198
 
195
199
  const agentId = owner.ref
196
- if (!getAgentRoster().includes(agentId)) {
200
+ if (!deps.agentConfig.roster.includes(agentId)) {
197
201
  return yield* new BadRequestError({ message: `Agent executor "${agentId}" is not registered.` })
198
202
  }
199
203
 
@@ -217,21 +221,23 @@ function createExecuteNode(deps: AgentExecutorDeps) {
217
221
  }
218
222
  const userRef = ensureRecordId(userRefSource, TABLES.USER)
219
223
  const userName = params.context.userName ?? 'User'
224
+ const runtimeAdapters = yield* RuntimeAdaptersServiceTag
220
225
  const { linearInstallation, githubInstallation, indexedRepoContext } = yield* Effect.all({
221
226
  linearInstallation: tryAgentExecutorTask(
222
227
  'get-linear-installation',
223
228
  `Failed to load Linear installation for org ${params.context.organizationId}.`,
224
- () => getLinearInstallationByOrgId(organizationRef),
229
+ () => getLinearInstallationByOrgId(deps.agentFactoryConfig.pluginRuntime, organizationRef),
225
230
  ),
226
231
  githubInstallation: tryAgentExecutorTask(
227
232
  'get-github-installation',
228
233
  `Failed to load GitHub installation for org ${params.context.organizationId}.`,
229
- () => getGithubInstallationForOrganization(params.context.organizationId),
234
+ () =>
235
+ getGithubInstallationForOrganization(deps.agentFactoryConfig.pluginRuntime, params.context.organizationId),
230
236
  ),
231
237
  indexedRepoContext: tryAgentExecutorTask(
232
238
  'build-indexed-repositories-context',
233
239
  `Failed to build indexed repository context for org ${params.context.organizationId}.`,
234
- () => buildIndexedRepositoriesContext(params.context.organizationId),
240
+ () => buildIndexedRepositoriesContext(runtimeAdapters, params.context.organizationId),
235
241
  ),
236
242
  }).pipe(Effect.withSpan('AgentExecutor.loadInstallations'))
237
243
 
@@ -246,7 +252,7 @@ function createExecuteNode(deps: AgentExecutorDeps) {
246
252
  }),
247
253
  ]
248
254
 
249
- const turnHooks = getTurnHooks()
255
+ const turnHooks = yield* TurnHooksServiceTag
250
256
  const agentResolutionValue = yield* tryAgentExecutorPromise(
251
257
  'resolve-agent',
252
258
  `Failed to resolve agent "${agentId}" for dispatched execution.`,
@@ -270,7 +276,7 @@ function createExecuteNode(deps: AgentExecutorDeps) {
270
276
  )
271
277
  const agentResolution = isRecord(agentResolutionValue) ? agentResolutionValue : null
272
278
  const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
273
- const runtimeConfig = getAgentRuntimeConfig({
279
+ const runtimeConfig = deps.agentFactoryConfig.getAgentRuntimeConfig({
274
280
  agentId: resolvedAgentId,
275
281
  threadType: thread.type,
276
282
  mode: dispatchMode,
@@ -283,7 +289,7 @@ function createExecuteNode(deps: AgentExecutorDeps) {
283
289
  responseGuardSection: buildOwnershipDispatchResponseGuard({ node: params.nodeSpec, executionMode: mode }),
284
290
  })
285
291
 
286
- const agentFactoryConfig = getResolvedAgentFactoryConfig()
292
+ const agentFactoryConfig = deps.agentFactoryConfig
287
293
  const rawTools = yield* tryAgentExecutorPromise(
288
294
  'build-agent-tools',
289
295
  `Failed to build agent tools for "${resolvedAgentId}".`,
@@ -400,13 +406,18 @@ function createExecuteNode(deps: AgentExecutorDeps) {
400
406
  }
401
407
 
402
408
  interface AgentExecutorDeps {
409
+ agentConfig: ResolvedAgentConfig
410
+ agentFactoryConfig: ResolvedAgentFactoryConfig
403
411
  db: SurrealDBService
404
412
  nodeWorkspaceService: ReturnType<typeof makeNodeWorkspaceService>
405
413
  writeIntentValidatorService: ReturnType<typeof makeWriteIntentValidatorService>
406
414
  }
407
415
 
408
416
  export function makeAgentExecutorService(deps: AgentExecutorDeps) {
409
- return { validateOwner, executeNode: createExecuteNode(deps) }
417
+ return {
418
+ validateOwner: (agentId: string, nodeId: string) => validateOwner(deps.agentConfig, agentId, nodeId),
419
+ executeNode: createExecuteNode(deps),
420
+ }
410
421
  }
411
422
 
412
423
  export class AgentExecutorServiceTag extends Context.Service<
@@ -417,9 +428,17 @@ export class AgentExecutorServiceTag extends Context.Service<
417
428
  export const AgentExecutorServiceLive = Layer.effect(
418
429
  AgentExecutorServiceTag,
419
430
  Effect.gen(function* () {
431
+ const agentConfig = yield* AgentConfigServiceTag
432
+ const agentFactoryConfig = yield* AgentFactoryServiceTag
420
433
  const db = yield* DatabaseServiceTag
421
434
  const nodeWorkspaceService = yield* NodeWorkspaceServiceTag
422
435
  const writeIntentValidatorService = yield* WriteIntentValidatorServiceTag
423
- return makeAgentExecutorService({ db, nodeWorkspaceService, writeIntentValidatorService })
436
+ return makeAgentExecutorService({
437
+ agentConfig,
438
+ agentFactoryConfig,
439
+ db,
440
+ nodeWorkspaceService,
441
+ writeIntentValidatorService,
442
+ })
424
443
  }),
425
444
  )
@@ -3,11 +3,7 @@ import { Context, Effect, Layer } from 'effect'
3
3
  import type { RecordIdRef } from '../db/record-id'
4
4
  import { recordIdToString } from '../db/record-id'
5
5
  import { TABLES } from '../db/tables'
6
- import { runPromise } from '../effect/runtime'
7
- import type {
8
- makeAttachmentStorageService,
9
- UploadedThreadAttachment as SdkUploadedThreadAttachment,
10
- } from '../storage/attachment-storage.service'
6
+ import type { makeAttachmentStorageService } from '../storage/attachment-storage.service'
11
7
  import { AttachmentStorageServiceTag } from '../storage/attachment-storage.service'
12
8
  import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
13
9
 
@@ -55,35 +51,11 @@ export function makeAttachmentService(attachmentStorage: ReturnType<typeof makeA
55
51
  })
56
52
  },
57
53
 
58
- extractStoredAttachmentText({
59
- storageKey,
60
- name,
61
- contentType,
62
- }: {
63
- storageKey: string
64
- name: string
65
- contentType: string
66
- }): Promise<string> {
67
- return runPromise(attachmentStorage.extractStoredAttachmentText({ storageKey, name, contentType }))
68
- },
69
-
70
- extractStoredAttachmentTextEffect(params: { storageKey: string; name: string; contentType: string }) {
54
+ extractStoredAttachmentText(params: { storageKey: string; name: string; contentType: string }) {
71
55
  return attachmentStorage.extractStoredAttachmentText(params)
72
56
  },
73
57
 
74
- extractStoredAttachmentPages({
75
- storageKey,
76
- name,
77
- contentType,
78
- }: {
79
- storageKey: string
80
- name: string
81
- contentType: string
82
- }): Promise<{ pageMode: 'logical' | 'pdf'; pages: string[] }> {
83
- return runPromise(attachmentStorage.extractStoredAttachmentPages({ storageKey, name, contentType }))
84
- },
85
-
86
- extractStoredAttachmentPagesEffect(params: { storageKey: string; name: string; contentType: string }) {
58
+ extractStoredAttachmentPages(params: { storageKey: string; name: string; contentType: string }) {
87
59
  return attachmentStorage.extractStoredAttachmentPages(params)
88
60
  },
89
61
 
@@ -125,30 +97,6 @@ export function makeAttachmentService(attachmentStorage: ReturnType<typeof makeA
125
97
  relativePath: string
126
98
  content: string
127
99
  contentType: string
128
- }): Promise<{ storageKey: string; sizeBytes: number }> {
129
- return runPromise(
130
- attachmentStorage.writeOrganizationDocument({
131
- orgId: toOrgId(orgId),
132
- namespace,
133
- relativePath,
134
- content,
135
- contentType,
136
- }),
137
- )
138
- },
139
-
140
- writeOrganizationDocumentEffect({
141
- orgId,
142
- namespace,
143
- relativePath,
144
- content,
145
- contentType,
146
- }: {
147
- orgId: RecordIdRef
148
- namespace: string
149
- relativePath: string
150
- content: string
151
- contentType: string
152
100
  }) {
153
101
  return attachmentStorage.writeOrganizationDocument({
154
102
  orgId: toOrgId(orgId),
@@ -173,21 +121,7 @@ export function makeAttachmentService(attachmentStorage: ReturnType<typeof makeA
173
121
  return attachmentStorage.uploadOrganizationDocument({ file, orgId: toOrgId(orgId), namespace, relativePath })
174
122
  },
175
123
 
176
- uploadThreadAttachment({
177
- file,
178
- orgId,
179
- userId,
180
- }: {
181
- file: File
182
- orgId: RecordIdRef
183
- userId: RecordIdRef
184
- }): Promise<SdkUploadedThreadAttachment> {
185
- return runPromise(
186
- attachmentStorage.uploadThreadAttachment({ file, orgId: toOrgId(orgId), userId: toUserId(userId) }),
187
- )
188
- },
189
-
190
- uploadThreadAttachmentEffect({ file, orgId, userId }: { file: File; orgId: RecordIdRef; userId: RecordIdRef }) {
124
+ uploadThreadAttachment({ file, orgId, userId }: { file: File; orgId: RecordIdRef; userId: RecordIdRef }) {
191
125
  return attachmentStorage.uploadThreadAttachment({ file, orgId: toOrgId(orgId), userId: toUserId(userId) })
192
126
  },
193
127
  }