@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,37 +1,58 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
+ import { Schema, Effect } from 'effect'
4
5
 
5
- import { createAgent, getAgentRuntimeConfig } from '../config/agent-defaults'
6
+ import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
6
7
  import { aiLogger } from '../config/logger'
7
8
  import type { RecordIdRef } from '../db/record-id'
8
9
  import { recordIdToString } from '../db/record-id'
9
10
  import { TABLES } from '../db/tables'
11
+ import { effectTryMaybeAsync as effectTryMaybeAsyncShared } from '../effect/helpers'
10
12
  import { readRuntimeAgentIdentityOverrides } from '../runtime/agent-identity-overrides'
11
13
  import { mergeInstructionSections } from '../runtime/instruction-sections'
12
14
  import { getRuntimeAdapters, getTurnHooks } from '../runtime/runtime-extensions'
13
15
  import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
14
- import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation-orchestrator'
15
- import type { DefaultRepoSections, TeamConsultationParticipantRunner } from '../runtime/team-consultation-orchestrator'
16
- import { buildTeamConsultationResponseGuard } from '../runtime/team-consultation-prompts'
16
+ import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation/team-consultation-orchestrator'
17
+ import type {
18
+ DefaultRepoSections,
19
+ TeamConsultationParticipantRunner,
20
+ } from '../runtime/team-consultation/team-consultation-orchestrator'
21
+ import { buildTeamConsultationResponseGuard } from '../runtime/team-consultation/team-consultation-prompts'
17
22
  import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
18
23
  import type { ReadableUploadMetadata } from '../services/attachment.service'
19
24
 
20
- async function buildTeamThinkAgentTools(
25
+ function buildTeamThinkAgentToolsEffect(
21
26
  params: LotaRuntimeTeamThinkToolsParams,
22
- ): Promise<{ tools: Record<string, unknown> }> {
27
+ ): Effect.Effect<{ tools: ToolSet }, TeamThinkRuntimeError> {
23
28
  const builder = getRuntimeAdapters().buildTeamThinkAgentTools
24
29
  if (!builder) {
25
- return { tools: {} }
30
+ return Effect.succeed({ tools: {} })
26
31
  }
27
32
 
28
- const result = await builder(params)
29
- return { tools: result.tools as Record<string, unknown> }
33
+ return effectTryMaybeAsync(() => builder(params), 'Failed to build team-think agent tools.')
30
34
  }
31
35
 
32
36
  const TEAM_THINK_AGENT_MAX_RETRIES = 1
33
37
  const TEAM_THINK_AGENT_MAX_STEPS = 3
34
38
 
39
+ class TeamThinkAgentFactoryNotConfiguredError extends Schema.TaggedErrorClass<TeamThinkAgentFactoryNotConfiguredError>()(
40
+ 'TeamThinkAgentFactoryNotConfiguredError',
41
+ { agentId: Schema.String },
42
+ ) {}
43
+
44
+ class TeamThinkRuntimeError extends Schema.TaggedErrorClass<TeamThinkRuntimeError>()('TeamThinkRuntimeError', {
45
+ message: Schema.String,
46
+ cause: Schema.optional(Schema.Defect),
47
+ }) {}
48
+
49
+ function effectTryMaybeAsync<A>(
50
+ evaluate: () => A | PromiseLike<A>,
51
+ message: string,
52
+ ): Effect.Effect<A, TeamThinkRuntimeError> {
53
+ return effectTryMaybeAsyncShared(evaluate, (error) => new TeamThinkRuntimeError({ message, cause: error }))
54
+ }
55
+
35
56
  export function createTeamThinkTool(params: {
36
57
  historyMessages: ChatMessage[]
37
58
  latestUserMessageId: string
@@ -55,82 +76,102 @@ export function createTeamThinkTool(params: {
55
76
  (params.context as Record<string, unknown> | null | undefined) ?? null,
56
77
  )
57
78
  const participantRunner: TeamConsultationParticipantRunner = {
58
- async buildParticipantAgent(agentId, runParams) {
59
- const dynamicInstructionSections = await params.getAdditionalInstructionSections?.()
60
- const agentResolution = asRecord(
61
- await getTurnHooks().resolveAgent?.({
62
- agentId,
63
- mode: 'fixedThreadMode',
64
- thread: null,
65
- threadRef: params.threadId,
66
- orgRef: params.orgId,
67
- userRef: params.userId,
68
- onboardingActive: false,
69
- linearInstalled: false,
70
- githubInstalled: params.githubInstalled,
71
- additionalInstructionSections: mergeInstructionSections(
72
- dynamicInstructionSections,
73
- params.additionalInstructionSections,
74
- ),
75
- context: (params.context as Record<string, unknown> | null | undefined) ?? null,
79
+ buildParticipantAgent(agentId, runParams) {
80
+ return Effect.runPromise(
81
+ Effect.gen(function* () {
82
+ const currentContext = yield* Effect.context()
83
+ const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
84
+ const dynamicInstructionSections = yield* effectTryMaybeAsync(
85
+ () => params.getAdditionalInstructionSections?.(),
86
+ 'Failed to load dynamic team-think instruction sections.',
87
+ )
88
+ const agentResolution = asRecord(
89
+ yield* effectTryMaybeAsync(
90
+ () =>
91
+ getTurnHooks().resolveAgent?.({
92
+ agentId,
93
+ mode: 'fixedThreadMode',
94
+ thread: null,
95
+ threadRef: params.threadId,
96
+ orgRef: params.orgId,
97
+ userRef: params.userId,
98
+ onboardingActive: false,
99
+ linearInstalled: false,
100
+ githubInstalled: params.githubInstalled,
101
+ additionalInstructionSections: mergeInstructionSections(
102
+ dynamicInstructionSections,
103
+ params.additionalInstructionSections,
104
+ ),
105
+ context: (params.context as Record<string, unknown> | null | undefined) ?? null,
106
+ }),
107
+ 'Failed to resolve team-think participant agent.',
108
+ ),
109
+ )
110
+ const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
111
+ const config = getAgentRuntimeConfig({
112
+ agentId: resolvedAgentId,
113
+ threadType: 'group' as const,
114
+ mode: 'fixedThreadMode',
115
+ onboardingActive: false,
116
+ linearInstalled: false,
117
+ systemWorkspaceDetails: runParams.systemWorkspaceDetails,
118
+ preSeededMemoriesSection: runParams.preSeededMemoriesSection,
119
+ retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
120
+ additionalInstructionSections: mergeInstructionSections(
121
+ dynamicInstructionSections,
122
+ params.additionalInstructionSections,
123
+ readInstructionSections(agentResolution?.additionalInstructionSections),
124
+ ),
125
+ responseGuardSection: buildTeamConsultationResponseGuard({
126
+ agentId: resolvedAgentId,
127
+ task: runParams.task,
128
+ }),
129
+ })
130
+ const { tools } = yield* buildTeamThinkAgentToolsEffect({
131
+ agentId: resolvedAgentId,
132
+ workspaceId: params.orgId,
133
+ userId: params.userId,
134
+ workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
135
+ threadId: params.threadId,
136
+ githubInstalled: params.githubInstalled,
137
+ provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
138
+ availableUploads: params.availableUploads,
139
+ defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
140
+ context: params.context,
141
+ toolProviders: params.toolProviders,
142
+ })
143
+ const agentId_ = config.id || resolvedAgentId
144
+ const configuredMaxSteps = config.maxSteps
145
+ const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
146
+ const agentFactory = getResolvedAgentFactoryConfig().createAgent[agentId_]
147
+ if (!agentFactory) {
148
+ return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
149
+ }
150
+ const agent = agentFactory({
151
+ mode: 'fixedThreadMode',
152
+ tools,
153
+ extraInstructions: config.extraInstructions,
154
+ maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
155
+ stopWhen: [stepCountIs(maxSteps)],
156
+ })
157
+ const observer = {
158
+ run: <T>(fn: () => T | Promise<T>): Promise<T> =>
159
+ runPromiseWithCurrentContext(effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`)),
160
+ recordError: (error: unknown) => {
161
+ aiLogger.error`Team-think participant failed (${agentId}): ${error}`
162
+ },
163
+ recordAbort: (error: unknown) => {
164
+ aiLogger.info`Team-think participant aborted (${agentId}): ${
165
+ error instanceof Error ? error.message : String(error)
166
+ }`
167
+ },
168
+ }
169
+ return {
170
+ agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
171
+ observer,
172
+ }
76
173
  }),
77
174
  )
78
- const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
79
- const config = getAgentRuntimeConfig({
80
- agentId: resolvedAgentId,
81
- threadType: 'group' as const,
82
- mode: 'fixedThreadMode',
83
- onboardingActive: false,
84
- linearInstalled: false,
85
- systemWorkspaceDetails: runParams.systemWorkspaceDetails,
86
- preSeededMemoriesSection: runParams.preSeededMemoriesSection,
87
- retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
88
- additionalInstructionSections: mergeInstructionSections(
89
- dynamicInstructionSections,
90
- params.additionalInstructionSections,
91
- readInstructionSections(agentResolution?.additionalInstructionSections),
92
- ),
93
- responseGuardSection: buildTeamConsultationResponseGuard({ agentId: resolvedAgentId, task: runParams.task }),
94
- })
95
- const { tools } = await buildTeamThinkAgentTools({
96
- agentId: resolvedAgentId,
97
- workspaceId: params.orgId,
98
- userId: params.userId,
99
- workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
100
- threadId: params.threadId,
101
- githubInstalled: params.githubInstalled,
102
- provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
103
- availableUploads: params.availableUploads,
104
- defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
105
- context: params.context,
106
- toolProviders: params.toolProviders,
107
- })
108
- const agentConfig = config as Record<string, unknown>
109
- const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : resolvedAgentId
110
- const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
111
- const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
112
- const agent = createAgent[agentId_]({
113
- mode: 'fixedThreadMode',
114
- tools,
115
- extraInstructions: agentConfig.extraInstructions,
116
- maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
117
- stopWhen: [stepCountIs(maxSteps)],
118
- })
119
- const observer = {
120
- run: async <T>(fn: () => T | Promise<T>): Promise<T> => fn(),
121
- recordError: (error: unknown) => {
122
- aiLogger.error`Team-think participant failed (${agentId}): ${error}`
123
- },
124
- recordAbort: (error: unknown) => {
125
- aiLogger.info`Team-think participant aborted (${agentId}): ${
126
- error instanceof Error ? error.message : String(error)
127
- }`
128
- },
129
- }
130
- return {
131
- agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
132
- observer,
133
- }
134
175
  },
135
176
  }
136
177
 
@@ -11,8 +11,9 @@ export const userQuestionsTool = {
11
11
  description:
12
12
  'Ask the user structured questions. UI renders them; do not repeat them in text. Turn ends after this call.',
13
13
  inputSchema: UserQuestionsArgsSchema,
14
- execute: async (_args: UserQuestionsArgs) => {
15
- return 'Questions have been presented to the user in the chat UI. Do NOT restate them in text, and do NOT generate further text or tool calls. Your turn is complete.'
16
- },
14
+ execute: (_args: UserQuestionsArgs) =>
15
+ Promise.resolve(
16
+ 'Questions have been presented to the user in the chat UI. Do NOT restate them in text, and do NOT generate further text or tool calls. Your turn is complete.',
17
+ ),
17
18
  }),
18
19
  } as const satisfies ToolDefinition<void>
@@ -0,0 +1,6 @@
1
+ export const WEB_TOOL_TIMEOUT_MS = 30_000
2
+
3
+ export function toRecord(value: unknown): Record<string, unknown> | null {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null
5
+ return value as Record<string, unknown>
6
+ }
@@ -1,30 +1,24 @@
1
+ import { Schema, Duration, Effect } from 'effect'
2
+
1
3
  import { serverLogger } from '../config/logger'
4
+ import { TimeoutError } from '../effect/errors'
2
5
  import { getErrorMessage } from './errors'
3
6
 
4
- class TimeoutError extends Error {
5
- constructor(operation: string, ms: number) {
6
- super(`${operation} timed out after ${ms}ms`)
7
- this.name = 'TimeoutError'
8
- }
9
- }
7
+ class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()('TimedOperationError', {
8
+ operation: Schema.String,
9
+ cause: Schema.Defect,
10
+ }) {}
10
11
 
11
- export async function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
12
- let timeoutId: ReturnType<typeof setTimeout> | undefined
13
-
14
- try {
15
- return await Promise.race([
16
- promise,
17
- new Promise<never>((_, reject) => {
18
- timeoutId = setTimeout(() => {
19
- reject(new TimeoutError(operation, ms))
20
- }, ms)
21
- }),
22
- ])
23
- } finally {
24
- if (timeoutId) {
25
- clearTimeout(timeoutId)
26
- }
27
- }
12
+ export function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
13
+ return Effect.runPromise(
14
+ Effect.tryPromise({ try: () => promise, catch: (cause) => new TimedOperationError({ operation, cause }) }).pipe(
15
+ Effect.timeout(Duration.millis(ms)),
16
+ Effect.catchTag('TimeoutError', () => Effect.fail(new TimeoutError({ operation, ms }))),
17
+ Effect.catchTag('TimedOperationError', (error) =>
18
+ Effect.fail(error.cause instanceof Error ? error.cause : new Error(String(error.cause))),
19
+ ),
20
+ ),
21
+ )
28
22
  }
29
23
 
30
24
  export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
@@ -0,0 +1,21 @@
1
+ import { RecordId } from 'surrealdb'
2
+
3
+ export function createSha256Hasher(): Bun.CryptoHasher {
4
+ return new Bun.CryptoHasher('sha256')
5
+ }
6
+
7
+ export function sha256Hex(value: string): string {
8
+ return createSha256Hasher().update(value).digest('hex')
9
+ }
10
+
11
+ export function sha256HexFromParts(parts: Iterable<string>): string {
12
+ const hasher = createSha256Hasher()
13
+ for (const part of parts) {
14
+ hasher.update(part)
15
+ }
16
+ return hasher.digest('hex')
17
+ }
18
+
19
+ export function createDeterministicRecordId(table: string, key: string): RecordId {
20
+ return new RecordId(table, sha256Hex(key))
21
+ }
@@ -1,3 +1,5 @@
1
+ import { DateTime } from 'effect'
2
+
1
3
  export { toIsoDateTimeString, toOptionalIsoDateTimeString } from '@lota-sdk/shared'
2
4
 
3
5
  const PROMPT_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
@@ -9,9 +11,46 @@ const PROMPT_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
9
11
 
10
12
  export function toDatabaseDateTime(value: string | Date | null | undefined): Date | undefined {
11
13
  if (value === null || value === undefined) return undefined
12
- return value instanceof Date ? value : new Date(value)
14
+ return value instanceof Date ? value : DateTime.toDateUtc(DateTime.makeUnsafe(value))
13
15
  }
14
16
 
15
17
  export function formatUtcPromptDate(value: Date): string {
16
18
  return PROMPT_DATE_FORMATTER.format(value)
17
19
  }
20
+
21
+ export function nowDateTime(): DateTime.Utc {
22
+ return DateTime.nowUnsafe()
23
+ }
24
+
25
+ export function nowEpochMillis(): number {
26
+ return nowDateTime().epochMilliseconds
27
+ }
28
+
29
+ export function nowDate(): Date {
30
+ return DateTime.toDateUtc(nowDateTime())
31
+ }
32
+
33
+ export function nowIsoDateTimeString(): string {
34
+ return DateTime.formatIso(nowDateTime())
35
+ }
36
+
37
+ export function unsafeDateFrom(value: string | number | Date): Date {
38
+ if (value instanceof Date) {
39
+ return value
40
+ }
41
+
42
+ const normalized = typeof value === 'number' && value < 1_000_000_000_000 ? value * 1000 : value
43
+ return DateTime.toDateUtc(DateTime.makeUnsafe(normalized))
44
+ }
45
+
46
+ export function unsafeDateFromUnknown(value: unknown): Date {
47
+ if (value instanceof Date) {
48
+ return value
49
+ }
50
+
51
+ if (typeof value === 'number') {
52
+ return unsafeDateFrom(value)
53
+ }
54
+
55
+ return DateTime.toDateUtc(DateTime.makeUnsafe(String(value)))
56
+ }
@@ -1,35 +1,52 @@
1
1
  export { getErrorMessage } from '@lota-sdk/shared'
2
+ import { Match } from 'effect'
3
+
4
+ import type { EffectError, ValidationIssue } from '../effect/errors'
5
+ import { BaseServicePersistenceError, isEffectError } from '../effect/errors'
2
6
 
3
7
  export function toError(value: unknown): Error {
4
8
  return value instanceof Error ? value : new Error(String(value))
5
9
  }
6
10
 
11
+ export interface HttpError {
12
+ statusCode: number
13
+ code: string
14
+ message: string
15
+ }
16
+
17
+ export interface AppErrorResponse {
18
+ status: number
19
+ body: { error: { code: string; message: string } }
20
+ }
21
+
22
+ export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => AppErrorResponse }
23
+
7
24
  export class AppError extends Error {
8
- public readonly code: string
9
- public readonly statusCode: number
25
+ readonly code: string
26
+ readonly statusCode: number
10
27
 
11
28
  constructor(message: string, code: string, statusCode: number) {
12
29
  super(message)
13
- this.name = this.constructor.name
30
+ this.name = new.target.name
14
31
  this.code = code
15
32
  this.statusCode = statusCode
16
- Error.captureStackTrace(this, this.constructor)
33
+ Object.setPrototypeOf(this, new.target.prototype)
17
34
  }
18
35
 
19
- public toResponse() {
36
+ toResponse(): AppErrorResponse {
20
37
  return { status: this.statusCode, body: { error: { code: this.code, message: this.message } } }
21
38
  }
22
39
  }
23
40
 
24
- export class NotFoundError extends AppError {
25
- constructor(message = 'Resource not found') {
26
- super(message, 'NOT_FOUND', 404)
41
+ export class BadRequestError extends AppError {
42
+ constructor(message: string) {
43
+ super(message, 'BAD_REQUEST', 400)
27
44
  }
28
45
  }
29
46
 
30
- export class BadRequestError extends AppError {
31
- constructor(message = 'Bad request') {
32
- super(message, 'BAD_REQUEST', 400)
47
+ export class NotFoundError extends AppError {
48
+ constructor(message: string) {
49
+ super(message, 'NOT_FOUND', 404)
33
50
  }
34
51
  }
35
52
 
@@ -44,11 +61,6 @@ type ErrorCode =
44
61
  | 'HTTP_ERROR'
45
62
  | 'TOO_MANY_REQUESTS'
46
63
 
47
- interface ValidationIssue {
48
- path: string
49
- message: string
50
- }
51
-
52
64
  export interface ErrorBody {
53
65
  error: { code: ErrorCode; message: string; issues?: ValidationIssue[] }
54
66
  }
@@ -73,3 +85,70 @@ export const errorResponses = {
73
85
  createValidationErrorResponse(message, issues),
74
86
  httpError: (message: string) => createErrorResponse('HTTP_ERROR', message),
75
87
  } as const
88
+
89
+ function httpError(message: string, code: string, statusCode: number): HttpError {
90
+ return { message, code, statusCode }
91
+ }
92
+
93
+ export function isAppErrorLike(error: unknown): error is AppErrorLike {
94
+ if (!(error instanceof Error)) {
95
+ return false
96
+ }
97
+
98
+ const candidate = error as Partial<AppErrorLike>
99
+ return typeof candidate.code === 'string' && typeof candidate.statusCode === 'number'
100
+ }
101
+
102
+ export function toAppErrorResponse(error: AppErrorLike): AppErrorResponse {
103
+ if (typeof error.toResponse === 'function') {
104
+ const response = error.toResponse()
105
+ if (
106
+ response &&
107
+ typeof response === 'object' &&
108
+ typeof response.status === 'number' &&
109
+ response.body &&
110
+ typeof response.body === 'object'
111
+ ) {
112
+ return response
113
+ }
114
+ }
115
+
116
+ return { status: error.statusCode, body: { error: { code: error.code, message: error.message } } }
117
+ }
118
+
119
+ function isBaseServicePersistenceError(error: EffectError): error is BaseServicePersistenceError {
120
+ return error._tag === BaseServicePersistenceError.name
121
+ }
122
+
123
+ const toHttpErrorMatch = Match.type<Exclude<EffectError, BaseServicePersistenceError>>().pipe(
124
+ Match.tag('NotFoundError', (e) => httpError(e.message, 'NOT_FOUND', 404)),
125
+ Match.tag('BadRequestError', (e) => httpError(e.message, 'BAD_REQUEST', 400)),
126
+ Match.tag('ValidationError', (e) => httpError(e.message, 'VALIDATION_ERROR', 400)),
127
+ Match.tag('ConflictError', (e) => httpError(e.message, 'CONFLICT', 409)),
128
+ Match.tag('ForbiddenError', (e) => httpError(e.message, 'FORBIDDEN', 403)),
129
+ Match.tag('ThreadTurnError', (e) =>
130
+ httpError(e.message, e.reason === 'conflict' ? 'CONFLICT' : 'BAD_REQUEST', e.reason === 'conflict' ? 409 : 400),
131
+ ),
132
+ Match.tag('ActiveThreadRunConflictError', (e) => httpError(e.message, 'CONFLICT', 409)),
133
+ Match.tag('ConfigurationError', (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
134
+ Match.tag('DatabaseError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
135
+ Match.tag('RedisError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
136
+ Match.tag('TimeoutError', (e) =>
137
+ httpError(`Operation "${e.operation}" timed out after ${e.ms}ms`, 'INTERNAL_SERVER_ERROR', 500),
138
+ ),
139
+ Match.tag('LockAcquisitionError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
140
+ Match.tag('LockLostError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
141
+ Match.tag('AiGenerationError', (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
142
+ Match.tag('ServiceError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
143
+ Match.exhaustive,
144
+ )
145
+
146
+ export function toHttpError(error: EffectError): HttpError {
147
+ if (isBaseServicePersistenceError(error)) {
148
+ return httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)
149
+ }
150
+
151
+ return toHttpErrorMatch(error)
152
+ }
153
+
154
+ export { isEffectError }
@@ -2,69 +2,54 @@ import type { ErrorHandler } from 'hono'
2
2
  import { HTTPException } from 'hono/http-exception'
3
3
  import { ZodError } from 'zod'
4
4
 
5
- import { AppError, getErrorMessage } from './errors'
6
-
7
- type AppErrorLike = Pick<AppError, 'code' | 'message' | 'statusCode' | 'toResponse'> & { name?: string }
5
+ import { toValidationIssues } from '../effect/zod'
6
+ import { getErrorMessage, isAppErrorLike, isEffectError, toAppErrorResponse, toHttpError } from './errors'
8
7
 
9
8
  type HonoErrorLogger = Pick<Console, 'warn' | 'error'>
10
9
 
11
- function isAppErrorLike(error: unknown): error is AppErrorLike {
12
- if (!error || typeof error !== 'object') {
13
- return false
14
- }
15
-
16
- const candidate = error as Partial<AppErrorLike>
17
- return (
18
- typeof candidate.code === 'string' &&
19
- typeof candidate.message === 'string' &&
20
- typeof candidate.statusCode === 'number' &&
21
- typeof candidate.toResponse === 'function'
22
- )
10
+ function createErrorResponse(code: string, message: string) {
11
+ return { error: { code, message } }
23
12
  }
24
13
 
25
14
  function createValidationErrorResponse(issues: Array<{ path: string; message: string }>) {
26
15
  return { error: { code: 'VALIDATION_ERROR', message: 'Validation failed', issues } }
27
16
  }
28
17
 
29
- function createHttpErrorResponse(message: string) {
30
- return { error: { code: 'HTTP_ERROR', message } }
31
- }
32
-
33
- function createServerErrorResponse(message: string) {
34
- return { error: { code: 'INTERNAL_SERVER_ERROR', message } }
35
- }
36
-
37
18
  export function createHonoErrorHandler(logger: HonoErrorLogger): ErrorHandler {
38
19
  return (error, c) => {
39
- const appError = error instanceof AppError || isAppErrorLike(error) ? error : null
40
-
41
- if (appError) {
42
- const log = appError.statusCode >= 500 ? logger.error : logger.warn
43
- const errorName = typeof appError.name === 'string' && appError.name.length > 0 ? appError.name : 'AppError'
44
- log(`Request failed: ${errorName} (${appError.code}) ${appError.message}`)
45
- const { status, body } = appError.toResponse()
46
- const typedStatus = status as Parameters<typeof c.json>[1]
47
- return c.json(body, typedStatus)
48
- }
49
-
50
20
  if (error instanceof HTTPException) {
51
21
  const log = error.status >= 500 ? logger.error : logger.warn
52
22
  log(`Request failed: HTTPException ${error.status} ${error.message}`)
53
- return error.res ?? c.json(createHttpErrorResponse(error.message), error.status)
23
+ return error.res ?? c.json(createErrorResponse('HTTP_ERROR', error.message), error.status)
54
24
  }
55
25
 
56
26
  if (error instanceof ZodError) {
57
27
  logger.warn(`Request failed: ZodError ${error.message}`)
58
- const issues = error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message }))
59
- return c.json(createValidationErrorResponse(issues), 400)
28
+ return c.json(createValidationErrorResponse([...toValidationIssues(error)]), 400)
29
+ }
30
+
31
+ if (isAppErrorLike(error)) {
32
+ const response = toAppErrorResponse(error)
33
+ const log = response.status >= 500 ? logger.error : logger.warn
34
+ log(`Request failed: ${error.name} ${error.message}`)
35
+ const typedStatus = response.status as Parameters<typeof c.json>[1]
36
+ return c.json(response.body, typedStatus)
37
+ }
38
+
39
+ if (isEffectError(error)) {
40
+ const httpError = toHttpError(error)
41
+ const log = httpError.statusCode >= 500 ? logger.error : logger.warn
42
+ log(`Request failed: ${error._tag} ${error.message}`)
43
+ const typedStatus = httpError.statusCode as Parameters<typeof c.json>[1]
44
+ return c.json(createErrorResponse(httpError.code, httpError.message), typedStatus)
60
45
  }
61
46
 
62
47
  if (error instanceof Error) {
63
48
  logger.error(`Server error: ${error.name} ${error.message}\n${error.stack ?? ''}`)
64
- return c.json(createServerErrorResponse('Internal Server Error'), 500)
49
+ return c.json(createErrorResponse('INTERNAL_SERVER_ERROR', 'Internal Server Error'), 500)
65
50
  }
66
51
 
67
52
  logger.error(`Server error: ${getErrorMessage(error)}`)
68
- return c.json(createServerErrorResponse('Internal Server Error'), 500)
53
+ return c.json(createErrorResponse('INTERNAL_SERVER_ERROR', 'Internal Server Error'), 500)
69
54
  }
70
55
  }
@@ -1,8 +1,9 @@
1
1
  export * from './async'
2
+ export * from './crypto'
2
3
  export * from './date-time'
3
- export * from './env'
4
4
  export * from './errors'
5
5
  export * from './hono-error-handler'
6
+ export * from './null-proto-record'
6
7
  export * from './sse-keepalive'
7
8
  export {
8
9
  CHARS_PER_TOKEN_ESTIMATE,