@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.
Files changed (182) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +164 -82
  4. package/src/ai-gateway/index.ts +16 -1
  5. package/src/config/agent-defaults.ts +4 -107
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/background-processing.ts +1 -1
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +22 -25
  10. package/src/config/thread-defaults.ts +1 -10
  11. package/src/create-runtime.ts +145 -670
  12. package/src/db/base.service.ts +30 -38
  13. package/src/db/memory-query-builder.ts +2 -1
  14. package/src/db/memory-store.ts +29 -20
  15. package/src/db/memory.ts +188 -195
  16. package/src/db/service-normalization.ts +97 -64
  17. package/src/db/service.ts +496 -384
  18. package/src/db/startup.ts +30 -19
  19. package/src/effect/helpers.ts +30 -5
  20. package/src/effect/index.ts +7 -7
  21. package/src/effect/layers.ts +75 -72
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -71
  24. package/src/index.ts +13 -12
  25. package/src/queues/autonomous-job.queue.ts +177 -143
  26. package/src/queues/context-compaction.queue.ts +41 -39
  27. package/src/queues/delayed-node-promotion.queue.ts +61 -42
  28. package/src/queues/document-processor.queue.ts +5 -3
  29. package/src/queues/index.ts +1 -0
  30. package/src/queues/memory-consolidation.queue.ts +79 -53
  31. package/src/queues/organization-learning.queue.ts +70 -33
  32. package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
  33. package/src/queues/plan-scheduler.queue.ts +101 -97
  34. package/src/queues/post-chat-memory.queue.ts +56 -46
  35. package/src/queues/queue-factory.ts +146 -69
  36. package/src/queues/queues.service.ts +61 -0
  37. package/src/queues/title-generation.queue.ts +44 -44
  38. package/src/redis/connection.ts +181 -164
  39. package/src/redis/org-memory-lock.ts +24 -9
  40. package/src/redis/redis-lease-lock.ts +8 -1
  41. package/src/redis/stream-context.ts +17 -9
  42. package/src/runtime/agent-identity-overrides.ts +7 -3
  43. package/src/runtime/agent-runtime-policy.ts +10 -5
  44. package/src/runtime/agent-stream-helpers.ts +24 -15
  45. package/src/runtime/chat-run-orchestration.ts +1 -1
  46. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  47. package/src/runtime/context-compaction/context-compaction.ts +131 -85
  48. package/src/runtime/domain-layer.ts +203 -0
  49. package/src/runtime/execution-plan-visibility.ts +5 -2
  50. package/src/runtime/graph-designer.ts +0 -14
  51. package/src/runtime/helper-model.ts +8 -4
  52. package/src/runtime/index.ts +1 -1
  53. package/src/runtime/indexed-repositories-policy.ts +2 -6
  54. package/src/runtime/memory/memory-block.ts +19 -9
  55. package/src/runtime/memory/memory-pipeline.ts +53 -66
  56. package/src/runtime/memory/memory-scope.ts +33 -29
  57. package/src/runtime/plugin-resolution.ts +58 -62
  58. package/src/runtime/post-turn-side-effects.ts +139 -161
  59. package/src/runtime/retrieval-adapters.ts +4 -4
  60. package/src/runtime/runtime-config.ts +3 -9
  61. package/src/runtime/runtime-extensions.ts +0 -43
  62. package/src/runtime/runtime-lifecycle.ts +124 -0
  63. package/src/runtime/runtime-services.ts +455 -0
  64. package/src/runtime/runtime-worker-registry.ts +113 -30
  65. package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
  66. package/src/runtime/social-chat/social-chat-history.ts +24 -13
  67. package/src/runtime/social-chat/social-chat.ts +420 -369
  68. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
  69. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  70. package/src/runtime/thread-chat-helpers.ts +18 -9
  71. package/src/runtime/thread-turn-context.ts +28 -74
  72. package/src/runtime/turn-lifecycle.ts +6 -14
  73. package/src/services/agent-activity.service.ts +169 -176
  74. package/src/services/agent-executor.service.ts +207 -196
  75. package/src/services/artifact.service.ts +10 -5
  76. package/src/services/attachment.service.ts +16 -48
  77. package/src/services/autonomous-job.service.ts +81 -87
  78. package/src/services/background-work.service.ts +54 -0
  79. package/src/services/chat-run-registry.service.ts +3 -1
  80. package/src/services/context-compaction.service.ts +8 -10
  81. package/src/services/document-chunk.service.ts +8 -17
  82. package/src/services/execution-plan/execution-plan-graph.ts +122 -109
  83. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  84. package/src/services/execution-plan/execution-plan.service.ts +68 -51
  85. package/src/services/feedback-loop.service.ts +1 -1
  86. package/src/services/global-orchestrator.service.ts +49 -15
  87. package/src/services/graph-full-routing.ts +49 -37
  88. package/src/services/index.ts +1 -0
  89. package/src/services/institutional-memory.service.ts +8 -17
  90. package/src/services/learned-skill.service.ts +38 -35
  91. package/src/services/memory/memory-conversation.ts +10 -5
  92. package/src/services/memory/memory-errors.ts +27 -0
  93. package/src/services/memory/memory-org-memory.ts +14 -3
  94. package/src/services/memory/memory-preseeded.ts +10 -4
  95. package/src/services/memory/memory-utils.ts +2 -1
  96. package/src/services/memory/memory.service.ts +37 -52
  97. package/src/services/memory/rerank.service.ts +3 -11
  98. package/src/services/monitoring-window.service.ts +1 -1
  99. package/src/services/mutating-approval.service.ts +1 -1
  100. package/src/services/node-workspace.service.ts +2 -2
  101. package/src/services/notification.service.ts +16 -4
  102. package/src/services/organization-member.service.ts +1 -1
  103. package/src/services/organization.service.ts +34 -51
  104. package/src/services/ownership-dispatcher.service.ts +148 -95
  105. package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
  106. package/src/services/plan/plan-agent-query.service.ts +13 -9
  107. package/src/services/plan/plan-approval.service.ts +52 -48
  108. package/src/services/plan/plan-artifact.service.ts +2 -2
  109. package/src/services/plan/plan-builder.service.ts +2 -2
  110. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  111. package/src/services/plan/plan-compiler.service.ts +1 -1
  112. package/src/services/plan/plan-completion-side-effects.ts +99 -113
  113. package/src/services/plan/plan-coordination.service.ts +1 -1
  114. package/src/services/plan/plan-cycle.service.ts +171 -202
  115. package/src/services/plan/plan-deadline.service.ts +304 -307
  116. package/src/services/plan/plan-event-delivery.service.ts +84 -72
  117. package/src/services/plan/plan-executor-context.ts +2 -0
  118. package/src/services/plan/plan-executor-graph.ts +375 -353
  119. package/src/services/plan/plan-executor-helpers.ts +60 -75
  120. package/src/services/plan/plan-executor.service.ts +494 -489
  121. package/src/services/plan/plan-run.service.ts +12 -19
  122. package/src/services/plan/plan-scheduler.service.ts +89 -82
  123. package/src/services/plan/plan-template.service.ts +1 -1
  124. package/src/services/plan/plan-transaction-events.ts +8 -5
  125. package/src/services/plan/plan-validator.service.ts +1 -1
  126. package/src/services/plan/plan-workspace.service.ts +17 -11
  127. package/src/services/plugin-executor.service.ts +26 -21
  128. package/src/services/quality-metrics.service.ts +1 -1
  129. package/src/services/queue-job.service.ts +8 -17
  130. package/src/services/recent-activity-title.service.ts +22 -10
  131. package/src/services/recent-activity.service.ts +1 -1
  132. package/src/services/skill-resolver.service.ts +1 -1
  133. package/src/services/social-chat-history.service.ts +37 -20
  134. package/src/services/system-executor.service.ts +25 -20
  135. package/src/services/thread/thread-bootstrap.ts +37 -19
  136. package/src/services/thread/thread-listing.ts +2 -1
  137. package/src/services/thread/thread-memory-block.ts +18 -5
  138. package/src/services/thread/thread-message.service.ts +30 -13
  139. package/src/services/thread/thread-title.service.ts +1 -1
  140. package/src/services/thread/thread-turn-execution.ts +87 -83
  141. package/src/services/thread/thread-turn-preparation.service.ts +65 -40
  142. package/src/services/thread/thread-turn-streaming.ts +32 -36
  143. package/src/services/thread/thread-turn.ts +43 -29
  144. package/src/services/thread/thread.service.ts +32 -8
  145. package/src/services/user.service.ts +1 -1
  146. package/src/services/write-intent-validator.service.ts +1 -1
  147. package/src/storage/attachment-storage.service.ts +7 -4
  148. package/src/storage/generated-document-storage.service.ts +1 -1
  149. package/src/system-agents/context-compaction.agent.ts +1 -1
  150. package/src/system-agents/helper-agent-options.ts +1 -1
  151. package/src/system-agents/memory-reranker.agent.ts +1 -1
  152. package/src/system-agents/memory.agent.ts +1 -1
  153. package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
  154. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  155. package/src/system-agents/skill-extractor.agent.ts +1 -1
  156. package/src/system-agents/skill-manager.agent.ts +1 -1
  157. package/src/system-agents/thread-router.agent.ts +23 -20
  158. package/src/system-agents/title-generator.agent.ts +1 -1
  159. package/src/tools/execution-plan.tool.ts +36 -20
  160. package/src/tools/fetch-webpage.tool.ts +30 -22
  161. package/src/tools/firecrawl-client.ts +1 -6
  162. package/src/tools/plan-approval.tool.ts +9 -1
  163. package/src/tools/remember-memory.tool.ts +3 -6
  164. package/src/tools/research-topic.tool.ts +12 -3
  165. package/src/tools/search-web.tool.ts +26 -18
  166. package/src/tools/search.tool.ts +4 -5
  167. package/src/tools/team-think.tool.ts +139 -121
  168. package/src/utils/async.ts +15 -6
  169. package/src/utils/errors.ts +27 -15
  170. package/src/workers/bootstrap.ts +34 -58
  171. package/src/workers/memory-consolidation.worker.ts +4 -1
  172. package/src/workers/organization-learning.worker.ts +16 -3
  173. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  174. package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
  175. package/src/workers/skill-extraction.runner.ts +13 -15
  176. package/src/workers/worker-utils.ts +14 -8
  177. package/src/config/search.ts +0 -3
  178. package/src/effect/awaitable-effect.ts +0 -87
  179. package/src/effect/runtime-ref.ts +0 -25
  180. package/src/effect/runtime.ts +0 -31
  181. package/src/redis/runtime-connection.ts +0 -10
  182. package/src/runtime/agent-types.ts +0 -1
@@ -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) {
@@ -446,14 +438,15 @@ export function createAgentActivityService(deps: AgentActivityDeps | (() => Agen
446
438
  export type AgentActivityService = ReturnType<typeof createAgentActivityService>
447
439
 
448
440
  export class AgentActivityServiceTag extends Context.Service<AgentActivityServiceTag, AgentActivityService>()(
449
- 'AgentActivityService',
441
+ '@lota-sdk/core/AgentActivityService',
450
442
  ) {}
451
443
 
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
  )