@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,28 +1,10 @@
1
+ import { Context, Schema, Effect, Fiber, Layer, PubSub, Stream } from 'effect'
1
2
  import type { Redis } from 'ioredis'
2
3
  import type { Publisher, Subscriber } from 'resumable-stream/ioredis'
3
4
  import { createResumableStreamContext } from 'resumable-stream/ioredis'
4
5
 
5
- import { getRedisConnection } from './connection-accessor'
6
-
7
- function toSubscriber(client: Redis): Subscriber {
8
- const handlers = new Map<string, (message: string) => void>()
9
- const messageListener = (channel: string, message: string) => {
10
- handlers.get(channel)?.(message)
11
- }
12
- return {
13
- connect: () => Promise.resolve(),
14
- subscribe: async (channel, callback) => {
15
- if (handlers.size === 0) client.on('message', messageListener)
16
- handlers.set(channel, callback)
17
- await client.subscribe(channel)
18
- },
19
- unsubscribe: async (channel) => {
20
- handlers.delete(channel)
21
- if (handlers.size === 0) client.removeListener('message', messageListener)
22
- return client.unsubscribe(channel)
23
- },
24
- }
25
- }
6
+ import { RedisServiceTag } from '../effect/services'
7
+ import type { RedisConnectionManager } from './connection'
26
8
 
27
9
  function toPublisher(client: Redis): Publisher {
28
10
  return {
@@ -34,35 +16,91 @@ function toPublisher(client: Redis): Publisher {
34
16
  }
35
17
  }
36
18
 
37
- let sharedSubscriber: { client: Redis; subscriber: Subscriber } | undefined
19
+ type SharedSubscriberEvent = { readonly channel: string; readonly message: string }
20
+
21
+ class SharedSubscriberCloseError extends Schema.TaggedErrorClass<SharedSubscriberCloseError>()(
22
+ 'SharedSubscriberCloseError',
23
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
24
+ ) {}
38
25
 
39
- function getSharedSubscriber(): Subscriber {
40
- if (!sharedSubscriber) {
26
+ export class SharedThreadStreamSubscriberTag extends Context.Service<
27
+ SharedThreadStreamSubscriberTag,
28
+ { readonly subscriber: Subscriber }
29
+ >()('SharedThreadStreamSubscriber') {}
30
+
31
+ export const SharedThreadStreamSubscriberLive = Layer.effect(
32
+ SharedThreadStreamSubscriberTag,
33
+ Effect.gen(function* () {
34
+ const redisManager = yield* RedisServiceTag
41
35
  // Disable enableReadyCheck — the ready check sends INFO which is rejected
42
36
  // on connections in subscribe mode, causing unhandled ioredis error events.
43
- const client = getRedisConnection().duplicate({ enableReadyCheck: false })
37
+ const client = redisManager.getConnection().duplicate({ enableReadyCheck: false })
44
38
  client.on('error', () => {}) // prevent [ioredis] Unhandled error event logs
45
- sharedSubscriber = { client, subscriber: toSubscriber(client) }
46
- }
47
- return sharedSubscriber.subscriber
48
- }
49
39
 
50
- export async function closeSharedSubscriber(): Promise<void> {
51
- if (!sharedSubscriber) return
52
- const { client } = sharedSubscriber
53
- sharedSubscriber = undefined
54
- try {
55
- await client.quit()
56
- } catch {
57
- client.disconnect()
58
- }
59
- }
40
+ const events = yield* PubSub.unbounded<SharedSubscriberEvent>()
41
+ const handlers = new Map<string, (message: string) => void>()
42
+
43
+ const dispatchFiber = yield* Effect.forkScoped(
44
+ Stream.fromPubSub(events).pipe(
45
+ Stream.runForEach((event) =>
46
+ Effect.sync(() => {
47
+ handlers.get(event.channel)?.(event.message)
48
+ }),
49
+ ),
50
+ ),
51
+ )
52
+
53
+ const currentContext = yield* Effect.context()
54
+ const runFork = Effect.runForkWith(currentContext)
55
+ const messageListener = (channel: string, message: string) => {
56
+ void runFork(PubSub.publish(events, { channel, message }).pipe(Effect.asVoid))
57
+ }
58
+ client.on('message', messageListener)
59
+
60
+ const subscribe = (channel: string, callback: (message: string) => void): Promise<void> => {
61
+ handlers.set(channel, callback)
62
+ return client.subscribe(channel).then(() => undefined)
63
+ }
64
+
65
+ const unsubscribe = (channel: string): Promise<void> => {
66
+ handlers.delete(channel)
67
+ return client.unsubscribe(channel).then(() => undefined)
68
+ }
69
+
70
+ const subscriber: Subscriber = {
71
+ connect: () => Promise.resolve(),
72
+ subscribe: (channel, callback) => subscribe(channel, callback),
73
+ unsubscribe: (channel) => unsubscribe(channel),
74
+ }
75
+
76
+ yield* Effect.addFinalizer(() =>
77
+ Effect.gen(function* () {
78
+ handlers.clear()
79
+ client.removeListener('message', messageListener)
80
+ yield* Fiber.interrupt(dispatchFiber)
81
+ yield* PubSub.shutdown(events)
82
+ yield* Effect.tryPromise({
83
+ try: () => client.quit(),
84
+ catch: (cause) =>
85
+ new SharedSubscriberCloseError({ message: 'Failed to quit shared stream subscriber.', cause }),
86
+ }).pipe(
87
+ Effect.catch(() =>
88
+ Effect.sync(() => {
89
+ client.disconnect()
90
+ }),
91
+ ),
92
+ )
93
+ }),
94
+ )
95
+
96
+ return { subscriber }
97
+ }),
98
+ )
60
99
 
61
- export function createThreadResumableContext() {
62
- const redis = getRedisConnection()
63
- return createResumableStreamContext({
64
- waitUntil: null,
65
- subscriber: getSharedSubscriber(),
66
- publisher: toPublisher(redis),
67
- })
100
+ export function createThreadResumableContext(
101
+ redisManager: Pick<RedisConnectionManager, 'getConnection'>,
102
+ subscriber: Subscriber,
103
+ ) {
104
+ const redis = redisManager.getConnection()
105
+ return createResumableStreamContext({ waitUntil: null, subscriber, publisher: toPublisher(redis) })
68
106
  }
@@ -1,4 +1,4 @@
1
- import { agentDisplayNames } from '../config/agent-defaults'
1
+ import { getAgentDisplayNames } from '../config/agent-defaults'
2
2
  import { asRecord, readOptionalString } from './thread-chat-helpers'
3
3
 
4
4
  interface RuntimeAgentIdentityOverrides {
@@ -58,5 +58,5 @@ export function resolveRuntimeAgentDisplayName(overrides: RuntimeAgentIdentityOv
58
58
  return override
59
59
  }
60
60
 
61
- return agentDisplayNames[agentId] ?? agentId
61
+ return getAgentDisplayNames()[agentId] ?? agentId
62
62
  }
@@ -162,10 +162,13 @@ export function buildThreadAgentToolPolicy<TAgent extends string, TSkill extends
162
162
  linearInstalled: boolean
163
163
  githubInstalled: boolean
164
164
  provideRepoTool: boolean
165
+ leadAgentId?: TAgent
166
+ onboardingOwnerAgentId?: TAgent
165
167
  getAgentSkills: (agentId: TAgent, mode: ChatMode) => TSkill[]
166
168
  }): AgentToolPolicy<TSkill> {
167
169
  const resolvedMode = params.mode ?? toChatMode(params.threadType)
168
- const onboardingOwnerAgentId = resolveOnboardingOwnerAgentId(getLeadAgentId()) as TAgent
170
+ const leadAgentId = params.leadAgentId ?? (getLeadAgentId() as TAgent)
171
+ const onboardingOwnerAgentId = params.onboardingOwnerAgentId ?? (resolveOnboardingOwnerAgentId(leadAgentId) as TAgent)
169
172
  const skills = resolveActiveAgentSkills({
170
173
  agentId: params.agentId,
171
174
  threadType: params.threadType,
@@ -1,7 +1,9 @@
1
1
  import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import type { LanguageModelUsage, UIMessageStreamOptions } from 'ai'
3
+ import { Duration, Effect, Fiber } from 'effect'
3
4
 
4
- import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
5
+ import { getAgentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
6
+ import { nowEpochMillis } from '../utils/date-time'
5
7
  import { readRecord as _readRecord } from '../utils/string'
6
8
 
7
9
  export function readFiniteNumber(value: unknown): number | undefined {
@@ -32,7 +34,7 @@ export function createAgentMessageMetadata(params: {
32
34
  }): NonNullable<UIMessageStreamOptions<ChatMessage>['messageMetadata']> {
33
35
  return ({ part }) => {
34
36
  if (part.type === 'start') {
35
- return { agentId: params.agentId, agentName: params.agentName, createdAt: Date.now() }
37
+ return { agentId: params.agentId, agentName: params.agentName, createdAt: nowEpochMillis() }
36
38
  }
37
39
 
38
40
  if (part.type === 'finish') {
@@ -100,13 +102,21 @@ export function createServerRunAbortController(externalAbortSignal?: AbortSignal
100
102
  export function createTimedAbortSignal(parentSignal: AbortSignal, timeoutMs: number) {
101
103
  const controller = new AbortController()
102
104
  let didTimeout = false
105
+ let disposed = false
103
106
  const abortFromParent = () => {
104
107
  controller.abort((parentSignal as AbortSignal & { reason?: unknown }).reason)
105
108
  }
106
- const timeoutId = setTimeout(() => {
107
- didTimeout = true
108
- controller.abort(new Error(`Timed out after ${timeoutMs}ms`))
109
- }, timeoutMs)
109
+ const timeoutFiber = Effect.runFork(
110
+ Effect.sleep(Duration.millis(timeoutMs)).pipe(
111
+ Effect.tap(() =>
112
+ Effect.sync(() => {
113
+ if (disposed || controller.signal.aborted) return
114
+ didTimeout = true
115
+ controller.abort(new Error(`Timed out after ${timeoutMs}ms`))
116
+ }),
117
+ ),
118
+ ),
119
+ )
110
120
 
111
121
  if (parentSignal.aborted) {
112
122
  abortFromParent()
@@ -118,14 +128,15 @@ export function createTimedAbortSignal(parentSignal: AbortSignal, timeoutMs: num
118
128
  signal: controller.signal,
119
129
  didTimeout: () => didTimeout,
120
130
  dispose: () => {
121
- clearTimeout(timeoutId)
131
+ disposed = true
122
132
  parentSignal.removeEventListener('abort', abortFromParent)
133
+ void Effect.runFork(Fiber.interrupt(timeoutFiber))
123
134
  },
124
135
  }
125
136
  }
126
137
 
127
138
  export function buildSpecialistTaskMessage(params: { agentId: string; task: string }): ChatMessage {
128
- const displayName = agentDisplayNames[params.agentId] ?? params.agentId
139
+ const displayName = getAgentDisplayNames()[params.agentId] ?? params.agentId
129
140
  const leadAgentDisplayName = getLeadAgentDisplayName()
130
141
  return {
131
142
  id: Bun.randomUUIDv7(),
@@ -133,6 +144,6 @@ export function buildSpecialistTaskMessage(params: { agentId: string; task: stri
133
144
  parts: [
134
145
  { type: 'text', text: [`${leadAgentDisplayName} request for ${displayName}:`, params.task.trim()].join('\n') },
135
146
  ],
136
- metadata: { createdAt: Date.now() },
147
+ metadata: { createdAt: nowEpochMillis() },
137
148
  }
138
149
  }
@@ -1,25 +1,108 @@
1
- const COMPACTION_POLL_INTERVAL_MS = 200
1
+ import { Cause, Context, Schema, Duration, Effect, Latch, Layer } from 'effect'
2
+
3
+ import { effectTryPromise } from '../effect/helpers'
4
+ import { nowEpochMillis } from '../utils/date-time'
5
+
6
+ const COMPACTION_WAIT_REFRESH_MS = 1_000
2
7
  const COMPACTION_MAX_WAIT_MS = 120_000
3
8
 
4
- export async function waitForCompactionIfNeeded<TEntity>(params: {
9
+ class WaitForCompactionError extends Schema.TaggedErrorClass<WaitForCompactionError>()('WaitForCompactionError', {
10
+ entityId: Schema.String,
11
+ entityLabel: Schema.String,
12
+ message: Schema.String,
13
+ cause: Schema.optional(Schema.Defect),
14
+ }) {}
15
+
16
+ function toWaitForCompactionError(params: {
5
17
  entityId: string
6
18
  entityLabel: string
7
- loadEntity: () => Promise<TEntity>
8
- isCompacting: (entity: TEntity) => boolean
9
- }): Promise<TEntity> {
10
- let entity = await params.loadEntity()
11
- if (!params.isCompacting(entity)) return entity
12
-
13
- const deadline = Date.now() + COMPACTION_MAX_WAIT_MS
14
- while (params.isCompacting(entity)) {
15
- if (Date.now() > deadline) {
16
- throw new Error(
17
- `${params.entityLabel} ${params.entityId} compaction did not complete within ${COMPACTION_MAX_WAIT_MS}ms`,
18
- )
19
- }
20
- await Bun.sleep(COMPACTION_POLL_INTERVAL_MS)
21
- entity = await params.loadEntity()
22
- }
19
+ cause: unknown
20
+ }): WaitForCompactionError {
21
+ return new WaitForCompactionError({
22
+ entityId: params.entityId,
23
+ entityLabel: params.entityLabel,
24
+ message: params.cause instanceof Error ? params.cause.message : String(params.cause),
25
+ cause: params.cause,
26
+ })
27
+ }
23
28
 
24
- return entity
29
+ interface CompactionCoordination {
30
+ readonly signal: (entityId: string, compacting: boolean) => Effect.Effect<void>
31
+ readonly waitIfNeeded: <TEntity>(params: {
32
+ entityId: string
33
+ entityLabel: string
34
+ loadEntity: () => PromiseLike<TEntity> | Effect.Effect<TEntity, unknown>
35
+ isCompacting: (entity: TEntity) => boolean
36
+ }) => Effect.Effect<TEntity, WaitForCompactionError>
25
37
  }
38
+
39
+ export class CompactionCoordinationTag extends Context.Service<CompactionCoordinationTag, CompactionCoordination>()(
40
+ 'CompactionCoordination',
41
+ ) {}
42
+
43
+ export const CompactionCoordinationLive = Layer.effect(
44
+ CompactionCoordinationTag,
45
+ Effect.sync((): CompactionCoordination => {
46
+ const latches = new Map<string, Latch.Latch>()
47
+
48
+ function getLatch(entityId: string): Latch.Latch {
49
+ let latch = latches.get(entityId)
50
+ if (!latch) {
51
+ latch = Latch.makeUnsafe(true)
52
+ latches.set(entityId, latch)
53
+ }
54
+ return latch
55
+ }
56
+
57
+ return {
58
+ signal: (entityId, compacting) =>
59
+ Effect.sync(() => {
60
+ const latch = getLatch(entityId)
61
+ if (compacting) {
62
+ Latch.closeUnsafe(latch)
63
+ } else {
64
+ Latch.openUnsafe(latch)
65
+ }
66
+ }),
67
+
68
+ waitIfNeeded: Effect.fn('CompactionCoordination.waitIfNeeded')(function* <TEntity>(params: {
69
+ entityId: string
70
+ entityLabel: string
71
+ loadEntity: () => PromiseLike<TEntity> | Effect.Effect<TEntity, unknown>
72
+ isCompacting: (entity: TEntity) => boolean
73
+ }) {
74
+ const deadline = nowEpochMillis() + COMPACTION_MAX_WAIT_MS
75
+ const latch = getLatch(params.entityId)
76
+ let entity = yield* effectTryPromise(
77
+ () => params.loadEntity(),
78
+ (cause) => toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
79
+ )
80
+
81
+ while (params.isCompacting(entity)) {
82
+ Latch.closeUnsafe(latch)
83
+ if (nowEpochMillis() > deadline) {
84
+ return yield* new WaitForCompactionError({
85
+ entityId: params.entityId,
86
+ entityLabel: params.entityLabel,
87
+ message: `${params.entityLabel} ${params.entityId} compaction did not complete within ${COMPACTION_MAX_WAIT_MS}ms`,
88
+ })
89
+ }
90
+
91
+ const remainingMs = Math.max(0, deadline - nowEpochMillis())
92
+ const refreshWindowMs = Math.min(COMPACTION_WAIT_REFRESH_MS, remainingMs)
93
+ yield* latch.await.pipe(
94
+ Effect.timeout(Duration.millis(refreshWindowMs)),
95
+ Effect.catchIf(Cause.isTimeoutError, () => Effect.void),
96
+ )
97
+ entity = yield* effectTryPromise(
98
+ () => params.loadEntity(),
99
+ (cause) => toWaitForCompactionError({ entityId: params.entityId, entityLabel: params.entityLabel, cause }),
100
+ )
101
+ }
102
+
103
+ Latch.openUnsafe(latch)
104
+ return entity
105
+ }),
106
+ }
107
+ }),
108
+ )
@@ -1,5 +1,9 @@
1
+ import { Effect, Fiber, FiberMap } from 'effect'
2
+
1
3
  export class ChatRunRegistry {
2
- private controllers = new Map<string, AbortController>()
4
+ private readonly controllers = new Map<string, AbortController>()
5
+
6
+ constructor(private readonly trackedRuns: FiberMap.FiberMap<string>) {}
3
7
 
4
8
  has(runId: string): boolean {
5
9
  return this.controllers.has(runId)
@@ -16,9 +20,39 @@ export class ChatRunRegistry {
16
20
  stop(runId: string, reason?: unknown): boolean {
17
21
  const controller = this.controllers.get(runId)
18
22
  if (!controller) return false
19
-
20
23
  this.controllers.delete(runId)
21
24
  controller.abort(reason)
25
+ void Effect.runFork(FiberMap.remove(this.trackedRuns, runId))
22
26
  return true
23
27
  }
28
+
29
+ trackRunEffect<A, E, R>(
30
+ runId: string,
31
+ controller: AbortController,
32
+ effect: Effect.Effect<A, E, R>,
33
+ ): Effect.Effect<A, E, R> {
34
+ return Effect.gen(
35
+ function* (this: ChatRunRegistry) {
36
+ this.controllers.set(runId, controller)
37
+ const fiber = yield* FiberMap.run(
38
+ this.trackedRuns,
39
+ runId,
40
+ effect.pipe(
41
+ Effect.ensuring(
42
+ Effect.sync(() => {
43
+ if (this.controllers.get(runId) === controller) {
44
+ this.controllers.delete(runId)
45
+ }
46
+ }),
47
+ ),
48
+ ),
49
+ )
50
+ return yield* Fiber.join(fiber)
51
+ }.bind(this),
52
+ )
53
+ }
54
+
55
+ stopEffect(runId: string, reason?: unknown): Effect.Effect<boolean> {
56
+ return Effect.sync(() => this.stop(runId, reason))
57
+ }
24
58
  }
@@ -0,0 +1,107 @@
1
+ import { Schema, Effect } from 'effect'
2
+
3
+ import { createContextCompactionAgent } from '../../system-agents/context-compaction.agent'
4
+ import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from '../helper-model'
5
+ import {
6
+ buildContextCompactionPrompt,
7
+ buildMemoryBlockCompactionPrompt,
8
+ ContextCompactionOutputSchema,
9
+ createContextCompactionRuntime,
10
+ parseCompactionOutput,
11
+ } from './context-compaction'
12
+ import type { ContextCompactionRunnerParams } from './context-compaction'
13
+ import {
14
+ COMPACTION_CHUNK_MAX_CHARS,
15
+ CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
16
+ CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
17
+ CONTEXT_COMPACTION_THRESHOLD_RATIO,
18
+ CONTEXT_OUTPUT_RESERVE_TOKENS,
19
+ CONTEXT_SAFETY_MARGIN_TOKENS,
20
+ SUMMARY_ROLLUP_MAX_TOKENS,
21
+ } from './context-compaction-constants'
22
+
23
+ const CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS = 512
24
+
25
+ interface HelperModelRuntime {
26
+ generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T>
27
+ generateHelperText(params: GenerateHelperTextParams): Promise<string>
28
+ }
29
+
30
+ interface CreateContextCompactionRuntimeDeps {
31
+ helperModelRuntime: HelperModelRuntime
32
+ now?: () => number
33
+ randomId?: () => string
34
+ }
35
+
36
+ class ContextCompactionRuntimeError extends Schema.TaggedErrorClass<ContextCompactionRuntimeError>()(
37
+ 'ContextCompactionRuntimeError',
38
+ { message: Schema.String, cause: Schema.Defect },
39
+ ) {}
40
+
41
+ function tryContextCompactionPromise<A>(
42
+ message: string,
43
+ thunk: () => PromiseLike<A>,
44
+ ): Effect.Effect<A, ContextCompactionRuntimeError> {
45
+ return Effect.tryPromise({
46
+ try: () => Promise.resolve(thunk()),
47
+ catch: (cause) => new ContextCompactionRuntimeError({ message, cause }),
48
+ })
49
+ }
50
+
51
+ function runContextCompacter(helperModelRuntime: HelperModelRuntime, params: ContextCompactionRunnerParams) {
52
+ return Effect.runPromise(
53
+ tryContextCompactionPromise('Failed to compact runtime context.', () =>
54
+ helperModelRuntime.generateHelperStructured({
55
+ tag: 'context-compaction',
56
+ createAgent: createContextCompactionAgent,
57
+ messages: [
58
+ {
59
+ role: 'user',
60
+ content: buildContextCompactionPrompt({
61
+ previousSummary: params.previousSummary,
62
+ transcript: params.transcript,
63
+ }),
64
+ },
65
+ ],
66
+ schema: ContextCompactionOutputSchema,
67
+ maxOutputTokens: 8_000,
68
+ }),
69
+ ).pipe(Effect.map(parseCompactionOutput)),
70
+ )
71
+ }
72
+
73
+ export function createWiredContextCompactionRuntime(deps: CreateContextCompactionRuntimeDeps) {
74
+ const { helperModelRuntime } = deps
75
+
76
+ const runtime = createContextCompactionRuntime({
77
+ runCompacter: (params) => runContextCompacter(helperModelRuntime, params),
78
+ now: deps.now,
79
+ randomId: deps.randomId,
80
+ thresholdRatio: CONTEXT_COMPACTION_THRESHOLD_RATIO,
81
+ outputReserveTokens: CONTEXT_OUTPUT_RESERVE_TOKENS,
82
+ safetyMarginTokens: CONTEXT_SAFETY_MARGIN_TOKENS,
83
+ compactionChunkMaxChars: COMPACTION_CHUNK_MAX_CHARS,
84
+ summaryRollupMaxTokens: SUMMARY_ROLLUP_MAX_TOKENS,
85
+ includedToolNames: CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
86
+ includedToolPrefixes: CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES,
87
+ })
88
+
89
+ function compactMemoryBlockSummary(params: { previousSummary: string; newEntriesText: string }): Promise<string> {
90
+ const previousSummary = params.previousSummary.trim()
91
+ const newEntriesText = params.newEntriesText.trim()
92
+ if (!previousSummary && !newEntriesText) return Promise.resolve('')
93
+
94
+ return Effect.runPromise(
95
+ tryContextCompactionPromise('Failed to compact memory block summary.', () =>
96
+ helperModelRuntime.generateHelperText({
97
+ tag: 'memory-block-compaction',
98
+ createAgent: createContextCompactionAgent,
99
+ messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
100
+ maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
101
+ }),
102
+ ),
103
+ )
104
+ }
105
+
106
+ return { ...runtime, compactMemoryBlockSummary }
107
+ }