@lota-sdk/core 0.4.7 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,23 +1,23 @@
1
1
  import type {
2
2
  AgentActivityCounts,
3
3
  AgentActivityEntry,
4
- AgentActivityResponse,
5
- AgentProjectEntry,
6
- MyTasksResponse,
7
4
  PlanBoardColumn,
8
- PlanBoardResponse,
9
5
  PlanNodeCard,
10
6
  PlanViewNode,
11
- PlanViewResponse,
12
7
  SerializableExecutionPlan,
13
8
  SerializablePlanNode,
14
9
  } from '@lota-sdk/shared'
10
+ import { Context, Schema, Effect, Layer } from 'effect'
15
11
 
16
- import { agentRoster } from '../config/agent-defaults'
12
+ import { getAgentRoster } from '../config/agent-defaults'
17
13
  import { serverLogger } from '../config/logger'
18
- import { executionPlanService } from './execution-plan.service'
19
- import { threadService } from './thread.service'
20
- import type { NormalizedThread } from './thread.types'
14
+ import { toAwaitableEffect } from '../effect/awaitable-effect'
15
+ import { unsafeDateFrom } from '../utils/date-time'
16
+ import type { makeExecutionPlanService } from './execution-plan/execution-plan.service'
17
+ import { ExecutionPlanServiceTag } from './execution-plan/execution-plan.service'
18
+ import type { makeThreadService } from './thread/thread.service'
19
+ import { ThreadServiceTag } from './thread/thread.service'
20
+ import type { NormalizedThread } from './thread/thread.types'
21
21
 
22
22
  const BOARD_COLUMN_ORDER = ['ready', 'running', 'awaiting-human', 'completed', 'blocked', 'failed'] as const
23
23
  type BoardColumnStatus = (typeof BOARD_COLUMN_ORDER)[number]
@@ -37,10 +37,15 @@ type ActivePlanEntry = {
37
37
  }
38
38
 
39
39
  type AgentActivityDeps = {
40
- executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForThread'>
41
- threadService: Pick<typeof threadService, 'listThreads'>
40
+ executionPlanService: Pick<ReturnType<typeof makeExecutionPlanService>, 'getActivePlansForThread'>
41
+ threadService: Pick<ReturnType<typeof makeThreadService>, 'listThreads'>
42
42
  }
43
43
 
44
+ class AgentActivityServiceError extends Schema.TaggedErrorClass<AgentActivityServiceError>()(
45
+ 'AgentActivityServiceError',
46
+ { message: Schema.String, cause: Schema.Defect },
47
+ ) {}
48
+
44
49
  function isPendingPlanApproval(plan: SerializableExecutionPlan): boolean {
45
50
  return plan.status === 'pending-approval'
46
51
  }
@@ -69,7 +74,7 @@ function createEmptyCounts(): AgentActivityCounts {
69
74
 
70
75
  function maxIsoDate(current: string | null, candidate: string): string {
71
76
  if (!current) return candidate
72
- return new Date(candidate).getTime() > new Date(current).getTime() ? candidate : current
77
+ return unsafeDateFrom(candidate).getTime() > unsafeDateFrom(current).getTime() ? candidate : current
73
78
  }
74
79
 
75
80
  function incrementCounts(counts: AgentActivityCounts, rawStatus: string): void {
@@ -148,188 +153,245 @@ function buildPlanViewNode(
148
153
  }
149
154
  }
150
155
 
151
- export class AgentActivityService {
152
- constructor(private readonly deps: AgentActivityDeps = { executionPlanService, threadService }) {}
153
-
154
- async getBoard(userRef: string, orgRef: string): Promise<PlanBoardResponse> {
155
- const activePlans = await this.getAllActivePlans(userRef, orgRef)
156
- const cards = activePlans
157
- .filter(({ plan }) => !isPendingPlanApproval(plan))
158
- .flatMap(({ plan, thread }) => plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)))
159
-
160
- const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
161
- status,
162
- label: COLUMN_LABELS[status],
163
- nodes: cards.filter((card) => card.status === status),
164
- }))
165
-
166
- return {
167
- columns,
168
- summary: {
169
- totalNodes: cards.length,
170
- completedNodes: cards.filter((card) => card.status === 'completed').length,
171
- activePlanCount: activePlans.length,
172
- pendingApprovalCount: countPendingPlanApprovals(activePlans) + cards.filter((card) => card.hasApproval).length,
173
- },
174
- }
175
- }
176
-
177
- async getPlanView(orgRef: string, planRunId: string, userRef: string): Promise<PlanViewResponse | null> {
178
- const activePlans = await this.getAllActivePlans(userRef, orgRef)
179
- const match = activePlans.find(({ plan }) => plan.runId === planRunId)
180
- if (!match) return null
181
-
182
- const { plan, thread } = match
183
- return {
184
- planRunId: plan.runId,
185
- title: plan.title,
186
- objective: plan.objective,
187
- status: plan.status,
188
- leadAgentId: plan.leadAgentId,
189
- progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
190
- nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
191
- edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
192
- }
193
- }
194
-
195
- async getMyTasks(userRef: string, orgRef: string): Promise<MyTasksResponse> {
196
- const activePlans = await this.getAllActivePlans(userRef, orgRef)
197
- const tasks: PlanNodeCard[] = []
198
- const pendingPlanApprovalCount = countPendingPlanApprovals(activePlans)
199
-
200
- for (const { plan, thread } of activePlans) {
201
- if (isPendingPlanApproval(plan)) {
202
- continue
156
+ 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
+ },
203
179
  }
180
+ }),
181
+ )
182
+ }
204
183
 
205
- for (const node of plan.nodes) {
206
- const humanOwned = node.owner.executorType === 'user'
207
- const awaitingHuman = node.status === 'awaiting-human'
208
- if (!humanOwned && !awaitingHuman) continue
209
-
210
- tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
184
+ 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 })),
211
201
  }
212
- }
202
+ }),
203
+ )
204
+ }
213
205
 
214
- return { tasks, pendingApprovalCount: pendingPlanApprovalCount + tasks.filter((task) => task.hasApproval).length }
215
- }
206
+ 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)
216
212
 
217
- async getAgentActivity(userRef: string, orgRef: string): Promise<AgentActivityResponse> {
218
- const activePlans = await this.getAllActivePlans(userRef, orgRef)
219
- const activityByAgent = new Map<string, AgentActivityEntry>()
213
+ for (const { plan, thread } of activePlans) {
214
+ if (isPendingPlanApproval(plan)) {
215
+ continue
216
+ }
220
217
 
221
- for (const agentId of agentRoster) {
222
- activityByAgent.set(agentId, this.createEmptyEntry(agentId))
223
- }
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
224
222
 
225
- for (const { plan, thread } of activePlans) {
226
- if (isPendingPlanApproval(plan)) {
227
- if (plan.leadAgentId.trim()) {
228
- const leadEntry = this.ensureEntry(activityByAgent, plan.leadAgentId)
229
- leadEntry.isLeadingActivePlan = true
230
- this.ensureProjectEntry(leadEntry.projects, {
231
- threadId: thread.id,
232
- threadTitle: thread.title,
233
- planRunId: plan.runId,
234
- planTitle: plan.title,
235
- status: plan.status,
236
- })
237
- leadEntry.isRunning = leadEntry.isRunning || thread.isRunning
238
- leadEntry.lastActiveAt = maxIsoDate(leadEntry.lastActiveAt, thread.updatedAt)
223
+ tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
239
224
  }
240
- continue
241
225
  }
242
226
 
243
- const involvedAgents = new Set<string>()
244
-
245
- for (const node of plan.nodes) {
246
- if (node.owner.executorType !== 'agent') continue
247
-
248
- const agentId = node.owner.ref
249
- const entry = this.ensureEntry(activityByAgent, agentId)
250
- involvedAgents.add(agentId)
251
- incrementCounts(entry.counts, node.status)
227
+ return { tasks, pendingApprovalCount: pendingPlanApprovalCount + tasks.filter((task) => task.hasApproval).length }
228
+ }),
229
+ )
230
+ }
252
231
 
253
- if (!isCompletedStatus(node.status)) {
254
- entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
255
- }
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
+ })
256
252
  }
257
253
 
258
- if (plan.leadAgentId.trim()) {
259
- const leadEntry = this.ensureEntry(activityByAgent, plan.leadAgentId)
260
- leadEntry.isLeadingActivePlan = true
261
- involvedAgents.add(plan.leadAgentId)
262
- }
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
+ }
263
282
 
264
- for (const agentId of involvedAgents) {
265
- const entry = this.ensureEntry(activityByAgent, agentId)
266
- this.ensureProjectEntry(entry.projects, {
267
- threadId: thread.id,
268
- threadTitle: thread.title,
269
- planRunId: plan.runId,
270
- planTitle: plan.title,
271
- status: plan.status,
272
- })
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
+ }
273
308
 
274
- entry.isRunning = entry.isRunning || thread.isRunning
275
- entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
276
- }
277
- }
309
+ if (plan.leadAgentId.trim()) {
310
+ const leadEntry = activityByAgent.get(plan.leadAgentId) ?? {
311
+ agentId: plan.leadAgentId,
312
+ counts: createEmptyCounts(),
313
+ tasks: [],
314
+ projects: [],
315
+ isLeadingActivePlan: false,
316
+ isRunning: false,
317
+ lastActiveAt: null,
318
+ }
319
+ activityByAgent.set(plan.leadAgentId, leadEntry)
320
+ leadEntry.isLeadingActivePlan = true
321
+ involvedAgents.add(plan.leadAgentId)
322
+ }
278
323
 
279
- const userTasks = await this.getMyTasks(userRef, orgRef)
280
- const agents = [...activityByAgent.values()].sort((left, right) => {
281
- const leftIndex = agentRoster.indexOf(left.agentId)
282
- const rightIndex = agentRoster.indexOf(right.agentId)
283
- if (leftIndex !== -1 || rightIndex !== -1) {
284
- if (leftIndex === -1) return 1
285
- if (rightIndex === -1) return -1
286
- return leftIndex - rightIndex
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({
330
+ threadId: thread.id,
331
+ threadTitle: thread.title,
332
+ planRunId: plan.runId,
333
+ planTitle: plan.title,
334
+ status: plan.status,
335
+ })
336
+ }
337
+
338
+ entry.isRunning = entry.isRunning || thread.isRunning
339
+ entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
340
+ }
287
341
  }
288
- return left.agentId.localeCompare(right.agentId)
289
- })
290
-
291
- return {
292
- agents,
293
- userActivity: {
294
- taskCount: userTasks.tasks.length,
295
- pendingApprovalCount: userTasks.pendingApprovalCount,
296
- awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
297
- lastActiveAt: activePlans.reduce<string | null>(
298
- (latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
299
- null,
300
- ),
301
- },
302
- totalActivePlans: activePlans.length,
303
- }
304
- }
305
342
 
306
- async getAllActivePlans(userRef: string, orgRef: string): Promise<ActivePlanEntry[]> {
307
- const threads = await this.listRelevantThreads(userRef, orgRef)
308
- const planResults = await Promise.all(
309
- threads.map(async (thread) => {
310
- try {
311
- const plans = await this.deps.executionPlanService.getActivePlansForThread(thread.id)
312
- return plans.map((plan) => ({ plan, thread }))
313
- } catch (error) {
314
- serverLogger.error`Failed to load active plans for thread ${thread.id}: ${error}`
315
- return []
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
316
350
  }
317
- }),
318
- )
319
-
320
- return planResults.flat()
321
- }
351
+ return left.agentId.localeCompare(right.agentId)
352
+ })
353
+
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,
366
+ }
367
+ }),
368
+ )
369
+ }
322
370
 
323
- private async listRelevantThreads(userRef: string, orgRef: string): Promise<NormalizedThread[]> {
324
- const [direct, core, group] = await Promise.all([
325
- this.deps.threadService.listThreads(userRef, orgRef, { type: 'default', includeArchived: false }),
326
- this.deps.threadService.listThreads(userRef, orgRef, { type: 'thread', includeArchived: false }),
327
- this.deps.threadService.listThreads(userRef, orgRef, {
328
- type: 'group',
329
- includeArchived: false,
330
- take: 500,
331
- page: 1,
332
- }),
371
+ export function getAllActivePlans(userRef: string, orgRef: string, deps: AgentActivityDeps) {
372
+ return Effect.gen(function* () {
373
+ const [direct, core, group] = yield* Effect.all([
374
+ deps.threadService
375
+ .listThreads(userRef, orgRef, { type: 'default', includeArchived: false })
376
+ .pipe(
377
+ Effect.mapError(
378
+ (cause) => new AgentActivityServiceError({ message: 'Failed to load default threads.', cause }),
379
+ ),
380
+ ),
381
+ deps.threadService
382
+ .listThreads(userRef, orgRef, { type: 'thread', includeArchived: false })
383
+ .pipe(
384
+ Effect.mapError(
385
+ (cause) => new AgentActivityServiceError({ message: 'Failed to load thread threads.', cause }),
386
+ ),
387
+ ),
388
+ deps.threadService
389
+ .listThreads(userRef, orgRef, { type: 'group', includeArchived: false, take: 500, page: 1 })
390
+ .pipe(
391
+ Effect.mapError(
392
+ (cause) => new AgentActivityServiceError({ message: 'Failed to load group threads.', cause }),
393
+ ),
394
+ ),
333
395
  ])
334
396
 
335
397
  const deduped = new Map<string, NormalizedThread>()
@@ -337,39 +399,61 @@ export class AgentActivityService {
337
399
  deduped.set(thread.id, thread)
338
400
  }
339
401
 
340
- return [...deduped.values()]
341
- }
402
+ const threads = [...deduped.values()]
403
+ const planResults = yield* Effect.all(
404
+ threads.map((thread) =>
405
+ Effect.catch(
406
+ deps.executionPlanService.getActivePlansForThread(thread.id).pipe(
407
+ Effect.mapError(
408
+ (cause) =>
409
+ new AgentActivityServiceError({
410
+ message: `Failed to load active plans for thread ${thread.id}.`,
411
+ cause,
412
+ }),
413
+ ),
414
+ Effect.map((plans) => plans.map((plan): ActivePlanEntry => ({ plan, thread }))),
415
+ ),
416
+ (error) =>
417
+ Effect.sync(() => {
418
+ serverLogger.error`Failed to load active plans for thread ${thread.id}: ${error}`
419
+ return [] as ActivePlanEntry[]
420
+ }),
421
+ ),
422
+ ),
423
+ )
342
424
 
343
- private createEmptyEntry(agentId: string): AgentActivityEntry {
344
- return {
345
- agentId,
346
- counts: createEmptyCounts(),
347
- tasks: [],
348
- projects: [],
349
- isLeadingActivePlan: false,
350
- isRunning: false,
351
- lastActiveAt: null,
352
- }
353
- }
425
+ return planResults.flat()
426
+ })
427
+ }
354
428
 
355
- private ensureEntry(entries: Map<string, AgentActivityEntry>, agentId: string): AgentActivityEntry {
356
- const existing = entries.get(agentId)
357
- if (existing) {
358
- return existing
359
- }
429
+ function resolveAgentActivityDeps(deps: AgentActivityDeps | (() => AgentActivityDeps)): AgentActivityDeps {
430
+ return typeof deps === 'function' ? deps() : deps
431
+ }
360
432
 
361
- const created = this.createEmptyEntry(agentId)
362
- entries.set(agentId, created)
363
- return created
433
+ export function createAgentActivityService(deps: AgentActivityDeps | (() => AgentActivityDeps)) {
434
+ return {
435
+ getBoard: (userRef: string, orgRef: string) => getBoard(userRef, orgRef, resolveAgentActivityDeps(deps)),
436
+ getPlanView: (orgRef: string, planRunId: string, userRef: string) =>
437
+ getPlanView(orgRef, planRunId, userRef, resolveAgentActivityDeps(deps)),
438
+ getMyTasks: (userRef: string, orgRef: string) => getMyTasks(userRef, orgRef, resolveAgentActivityDeps(deps)),
439
+ getAgentActivity: (userRef: string, orgRef: string) =>
440
+ getAgentActivity(userRef, orgRef, resolveAgentActivityDeps(deps)),
441
+ getAllActivePlans: (userRef: string, orgRef: string) =>
442
+ getAllActivePlans(userRef, orgRef, resolveAgentActivityDeps(deps)),
364
443
  }
444
+ }
365
445
 
366
- private ensureProjectEntry(projects: AgentProjectEntry[], next: AgentProjectEntry): void {
367
- if (projects.some((project) => project.planRunId === next.planRunId)) {
368
- return
369
- }
446
+ export type AgentActivityService = ReturnType<typeof createAgentActivityService>
370
447
 
371
- projects.push(next)
372
- }
373
- }
448
+ export class AgentActivityServiceTag extends Context.Service<AgentActivityServiceTag, AgentActivityService>()(
449
+ 'AgentActivityService',
450
+ ) {}
374
451
 
375
- export const agentActivityService = new AgentActivityService()
452
+ export const AgentActivityServiceLive = Layer.effect(
453
+ AgentActivityServiceTag,
454
+ Effect.gen(function* () {
455
+ const executionPlanService = yield* ExecutionPlanServiceTag
456
+ const threadService = yield* ThreadServiceTag
457
+ return createAgentActivityService({ executionPlanService, threadService })
458
+ }),
459
+ )