@lota-sdk/core 0.4.9 → 0.4.10

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 (158) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +38 -10
  4. package/src/config/agent-defaults.ts +22 -9
  5. package/src/config/agent-types.ts +1 -1
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/index.ts +0 -1
  8. package/src/config/logger.ts +20 -7
  9. package/src/config/thread-defaults.ts +12 -4
  10. package/src/create-runtime.ts +69 -656
  11. package/src/db/memory-query-builder.ts +2 -1
  12. package/src/db/memory-store.ts +29 -20
  13. package/src/db/memory.ts +188 -195
  14. package/src/db/service-normalization.ts +97 -64
  15. package/src/db/service.ts +706 -538
  16. package/src/db/startup.ts +30 -19
  17. package/src/effect/awaitable-effect.ts +46 -37
  18. package/src/effect/helpers.ts +30 -5
  19. package/src/effect/index.ts +7 -5
  20. package/src/effect/layers.ts +82 -72
  21. package/src/effect/runtime.ts +18 -3
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -66
  24. package/src/index.ts +13 -11
  25. package/src/queues/autonomous-job.queue.ts +59 -71
  26. package/src/queues/context-compaction.queue.ts +6 -18
  27. package/src/queues/delayed-node-promotion.queue.ts +9 -17
  28. package/src/queues/organization-learning.queue.ts +17 -4
  29. package/src/queues/plan-agent-heartbeat.queue.ts +23 -20
  30. package/src/queues/plan-scheduler.queue.ts +6 -18
  31. package/src/queues/post-chat-memory.queue.ts +6 -18
  32. package/src/queues/queue-factory.ts +128 -50
  33. package/src/queues/title-generation.queue.ts +6 -17
  34. package/src/redis/connection.ts +181 -164
  35. package/src/redis/runtime-connection.ts +13 -3
  36. package/src/redis/stream-context.ts +17 -9
  37. package/src/runtime/agent-runtime-policy.ts +1 -1
  38. package/src/runtime/agent-stream-helpers.ts +15 -11
  39. package/src/runtime/chat-run-orchestration.ts +1 -1
  40. package/src/runtime/context-compaction/context-compaction-runtime.ts +1 -1
  41. package/src/runtime/context-compaction/context-compaction.ts +126 -82
  42. package/src/runtime/domain-layer.ts +192 -0
  43. package/src/runtime/graph-designer.ts +15 -7
  44. package/src/runtime/helper-model.ts +8 -4
  45. package/src/runtime/index.ts +0 -1
  46. package/src/runtime/memory/memory-block.ts +19 -9
  47. package/src/runtime/memory/memory-pipeline.ts +53 -66
  48. package/src/runtime/memory/memory-scope.ts +33 -29
  49. package/src/runtime/plugin-resolution.ts +33 -54
  50. package/src/runtime/post-turn-side-effects.ts +6 -26
  51. package/src/runtime/retrieval-adapters.ts +4 -4
  52. package/src/runtime/runtime-accessors.ts +92 -0
  53. package/src/runtime/runtime-config.ts +3 -3
  54. package/src/runtime/runtime-extensions.ts +20 -9
  55. package/src/runtime/runtime-lifecycle.ts +124 -0
  56. package/src/runtime/runtime-services.ts +386 -0
  57. package/src/runtime/runtime-token.ts +47 -0
  58. package/src/runtime/social-chat/social-chat-agent-runner.ts +7 -5
  59. package/src/runtime/social-chat/social-chat-history.ts +21 -12
  60. package/src/runtime/social-chat/social-chat.ts +401 -365
  61. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +58 -52
  62. package/src/runtime/thread-turn-context.ts +21 -27
  63. package/src/services/agent-activity.service.ts +1 -1
  64. package/src/services/agent-executor.service.ts +179 -187
  65. package/src/services/artifact.service.ts +10 -5
  66. package/src/services/attachment.service.ts +35 -1
  67. package/src/services/autonomous-job.service.ts +58 -56
  68. package/src/services/background-work.service.ts +54 -0
  69. package/src/services/chat-run-registry.service.ts +3 -1
  70. package/src/services/context-compaction.service.ts +1 -1
  71. package/src/services/document-chunk.service.ts +8 -17
  72. package/src/services/execution-plan/execution-plan-graph.ts +74 -52
  73. package/src/services/execution-plan/execution-plan.service.ts +1 -1
  74. package/src/services/feedback-loop.service.ts +1 -1
  75. package/src/services/global-orchestrator.service.ts +33 -10
  76. package/src/services/graph-full-routing.ts +44 -33
  77. package/src/services/index.ts +1 -0
  78. package/src/services/institutional-memory.service.ts +8 -17
  79. package/src/services/learned-skill.service.ts +38 -35
  80. package/src/services/memory/memory-errors.ts +27 -0
  81. package/src/services/memory/memory-org-memory.ts +14 -3
  82. package/src/services/memory/memory-preseeded.ts +10 -4
  83. package/src/services/memory/memory-utils.ts +2 -1
  84. package/src/services/memory/memory.service.ts +26 -44
  85. package/src/services/memory/rerank.service.ts +3 -11
  86. package/src/services/monitoring-window.service.ts +1 -1
  87. package/src/services/mutating-approval.service.ts +1 -1
  88. package/src/services/node-workspace.service.ts +2 -2
  89. package/src/services/notification.service.ts +16 -4
  90. package/src/services/organization-member.service.ts +1 -1
  91. package/src/services/organization.service.ts +34 -51
  92. package/src/services/ownership-dispatcher.service.ts +132 -90
  93. package/src/services/plan/plan-agent-heartbeat.service.ts +1 -1
  94. package/src/services/plan/plan-agent-query.service.ts +1 -1
  95. package/src/services/plan/plan-approval.service.ts +52 -48
  96. package/src/services/plan/plan-artifact.service.ts +2 -2
  97. package/src/services/plan/plan-builder.service.ts +2 -2
  98. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  99. package/src/services/plan/plan-compiler.service.ts +1 -1
  100. package/src/services/plan/plan-completion-side-effects.ts +18 -24
  101. package/src/services/plan/plan-coordination.service.ts +1 -1
  102. package/src/services/plan/plan-cycle.service.ts +171 -164
  103. package/src/services/plan/plan-deadline.service.ts +290 -304
  104. package/src/services/plan/plan-event-delivery.service.ts +44 -39
  105. package/src/services/plan/plan-executor-graph.ts +114 -67
  106. package/src/services/plan/plan-executor-helpers.ts +60 -75
  107. package/src/services/plan/plan-executor.service.ts +550 -467
  108. package/src/services/plan/plan-run.service.ts +12 -19
  109. package/src/services/plan/plan-scheduler.service.ts +27 -33
  110. package/src/services/plan/plan-template.service.ts +1 -1
  111. package/src/services/plan/plan-transaction-events.ts +8 -5
  112. package/src/services/plan/plan-validator.service.ts +1 -1
  113. package/src/services/plan/plan-workspace.service.ts +17 -11
  114. package/src/services/plugin-executor.service.ts +26 -21
  115. package/src/services/quality-metrics.service.ts +1 -1
  116. package/src/services/queue-job.service.ts +8 -17
  117. package/src/services/recent-activity-title.service.ts +17 -9
  118. package/src/services/recent-activity.service.ts +1 -1
  119. package/src/services/skill-resolver.service.ts +1 -1
  120. package/src/services/social-chat-history.service.ts +37 -20
  121. package/src/services/system-executor.service.ts +25 -20
  122. package/src/services/thread/thread-bootstrap.ts +26 -10
  123. package/src/services/thread/thread-listing.ts +2 -1
  124. package/src/services/thread/thread-memory-block.ts +18 -5
  125. package/src/services/thread/thread-message.service.ts +24 -8
  126. package/src/services/thread/thread-title.service.ts +1 -1
  127. package/src/services/thread/thread-turn-execution.ts +1 -1
  128. package/src/services/thread/thread-turn-preparation.service.ts +18 -16
  129. package/src/services/thread/thread-turn-streaming.ts +12 -11
  130. package/src/services/thread/thread-turn.ts +43 -10
  131. package/src/services/thread/thread.service.ts +11 -2
  132. package/src/services/user.service.ts +1 -1
  133. package/src/services/write-intent-validator.service.ts +1 -1
  134. package/src/storage/attachment-storage.service.ts +7 -4
  135. package/src/storage/generated-document-storage.service.ts +1 -1
  136. package/src/system-agents/context-compaction.agent.ts +1 -1
  137. package/src/system-agents/helper-agent-options.ts +1 -1
  138. package/src/system-agents/memory-reranker.agent.ts +1 -1
  139. package/src/system-agents/memory.agent.ts +1 -1
  140. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  141. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  142. package/src/system-agents/skill-extractor.agent.ts +1 -1
  143. package/src/system-agents/skill-manager.agent.ts +1 -1
  144. package/src/system-agents/title-generator.agent.ts +1 -1
  145. package/src/tools/execution-plan.tool.ts +28 -17
  146. package/src/tools/fetch-webpage.tool.ts +20 -13
  147. package/src/tools/firecrawl-client.ts +13 -3
  148. package/src/tools/plan-approval.tool.ts +9 -1
  149. package/src/tools/search-web.tool.ts +16 -9
  150. package/src/tools/team-think.tool.ts +2 -2
  151. package/src/utils/async.ts +15 -6
  152. package/src/utils/errors.ts +27 -15
  153. package/src/workers/bootstrap.ts +25 -48
  154. package/src/workers/organization-learning.worker.ts +1 -1
  155. package/src/workers/regular-chat-memory-digest.runner.ts +25 -15
  156. package/src/workers/worker-utils.ts +20 -2
  157. package/src/config/search.ts +0 -3
  158. package/src/runtime/agent-types.ts +0 -1
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Plugin database connect/disconnect helpers and runtime disconnect assembly.
3
+ *
4
+ * The plugin connect/disconnect helpers run inside the supplied
5
+ * `ManagedRuntime` so spans, logging, and layer context stay consistent with
6
+ * the rest of the SDK runtime. `createRuntimeDisconnect` instead drives the
7
+ * shutdown sequence through the caller-supplied `runPromiseWithCurrentContext`
8
+ * (currently `Effect.runPromiseWith(Context.empty())`), which intentionally
9
+ * does not preserve the SDK runtime context — by then the runtime is being
10
+ * torn down.
11
+ */
12
+
13
+ import type { ManagedRuntime } from 'effect'
14
+ import { Effect } from 'effect'
15
+
16
+ import { effectTryPromise } from '../effect/helpers'
17
+ import type { LotaPlugin } from './plugin-types'
18
+
19
+ // eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
20
+ type SdkManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
21
+
22
+ function getPluginLifecycleServices(plugin: LotaPlugin): Record<string, unknown> {
23
+ return plugin.services
24
+ }
25
+
26
+ /**
27
+ * Build a plugin database connector that iterates the configured plugins and
28
+ * calls each `services.connectDatabase()` once, tracking completion in
29
+ * `connectedPluginDatabases` so repeated calls are idempotent.
30
+ */
31
+ export function createPluginDatabaseConnector(
32
+ managedRuntime: SdkManagedRuntime,
33
+ pluginRuntime: Record<string, LotaPlugin>,
34
+ connectedPluginDatabases: Set<string>,
35
+ ): () => Promise<void> {
36
+ return () =>
37
+ managedRuntime.runPromise(
38
+ Effect.gen(function* () {
39
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
40
+ if (connectedPluginDatabases.has(pluginName)) {
41
+ continue
42
+ }
43
+
44
+ const services = getPluginLifecycleServices(plugin)
45
+ const connectDatabase = Reflect.get(services, 'connectDatabase')
46
+ if (typeof connectDatabase !== 'function') {
47
+ continue
48
+ }
49
+
50
+ const connectDatabaseFn = connectDatabase as (this: typeof services) => Promise<void>
51
+ yield* effectTryPromise(() => connectDatabaseFn.call(services))
52
+ connectedPluginDatabases.add(pluginName)
53
+ }
54
+ }),
55
+ )
56
+ }
57
+
58
+ /**
59
+ * Build a plugin database disconnector. Clears `connectedPluginDatabases`
60
+ * entries regardless of whether the plugin provides a `disconnectDatabase`
61
+ * hook, so the set stays in sync with actual connection state.
62
+ */
63
+ export function createPluginDatabaseDisconnector(
64
+ managedRuntime: SdkManagedRuntime,
65
+ pluginRuntime: Record<string, LotaPlugin>,
66
+ connectedPluginDatabases: Set<string>,
67
+ ): () => Promise<void> {
68
+ return () =>
69
+ managedRuntime.runPromise(
70
+ Effect.gen(function* () {
71
+ for (const [pluginName, plugin] of Object.entries(pluginRuntime)) {
72
+ if (!connectedPluginDatabases.has(pluginName)) {
73
+ continue
74
+ }
75
+
76
+ const services = getPluginLifecycleServices(plugin)
77
+ const disconnectDatabase = Reflect.get(services, 'disconnectDatabase')
78
+ if (typeof disconnectDatabase !== 'function') {
79
+ connectedPluginDatabases.delete(pluginName)
80
+ continue
81
+ }
82
+
83
+ const disconnectDatabaseFn = disconnectDatabase as (this: typeof services) => Promise<void>
84
+ yield* effectTryPromise(() => disconnectDatabaseFn.call(services))
85
+ connectedPluginDatabases.delete(pluginName)
86
+ }
87
+ }),
88
+ )
89
+ }
90
+
91
+ interface CreateDisconnectInput {
92
+ managedRuntime: SdkManagedRuntime
93
+ runPromiseWithCurrentContext: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
94
+ socialChatShutdown: () => Promise<void>
95
+ disconnectPluginDatabases: () => Promise<void>
96
+ onFinalize: () => void
97
+ }
98
+
99
+ /**
100
+ * Compose the runtime `disconnect()` function. The returned function is
101
+ * idempotent: the first call starts the shutdown sequence, subsequent calls
102
+ * return the same in-flight promise.
103
+ */
104
+ export function createRuntimeDisconnect(input: CreateDisconnectInput): () => Promise<void> {
105
+ const { managedRuntime, runPromiseWithCurrentContext, socialChatShutdown, disconnectPluginDatabases, onFinalize } =
106
+ input
107
+
108
+ let disconnectPromise: Promise<void> | null = null
109
+
110
+ return () => {
111
+ if (disconnectPromise) {
112
+ return disconnectPromise
113
+ }
114
+
115
+ disconnectPromise = runPromiseWithCurrentContext(
116
+ Effect.gen(function* () {
117
+ yield* Effect.ignore(effectTryPromise(() => socialChatShutdown()))
118
+ yield* Effect.ignore(effectTryPromise(() => disconnectPluginDatabases()))
119
+ yield* effectTryPromise(() => managedRuntime.dispose())
120
+ }).pipe(Effect.ensuring(Effect.sync(onFinalize))),
121
+ )
122
+ return disconnectPromise
123
+ }
124
+ }
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Eagerly resolves the full SDK service surface against a live ManagedRuntime.
3
+ *
4
+ * The previous implementation used per-property `get` Proxies that re-resolved
5
+ * each tag on every access. This module resolves every tag exactly once when
6
+ * the runtime starts and returns a plain object whose shape matches the
7
+ * `LotaRuntime['services']` + `LotaRuntime['lota']` contracts byte-for-byte.
8
+ */
9
+
10
+ import type { ChatMessage } from '@lota-sdk/shared'
11
+ import type { Context, ManagedRuntime } from 'effect'
12
+ import { Effect } from 'effect'
13
+
14
+ import { ensureRecordId } from '../db/record-id'
15
+ import type { SurrealDBService } from '../db/service'
16
+ import { TABLES } from '../db/tables'
17
+ import type { AwaitableService, AwaitableValue } from '../effect/awaitable-effect'
18
+ import { toAwaitableService } from '../effect/awaitable-effect'
19
+ import { BadRequestError, NotFoundError } from '../effect/errors'
20
+ import { effectTryPromise } from '../effect/helpers'
21
+ import type { RedisConnectionManager } from '../redis/connection'
22
+ import type { SharedThreadStreamSubscriberTag } from '../redis/stream-context'
23
+ import { AgentActivityServiceTag } from '../services/agent-activity.service'
24
+ import { ArtifactServiceTag } from '../services/artifact.service'
25
+ import { AttachmentServiceTag } from '../services/attachment.service'
26
+ import { AutonomousJobServiceTag } from '../services/autonomous-job.service'
27
+ import { ContextCompactionServiceTag } from '../services/context-compaction.service'
28
+ import { DocumentChunkServiceTag } from '../services/document-chunk.service'
29
+ import { ExecutionPlanServiceTag } from '../services/execution-plan/execution-plan.service'
30
+ import { MemoryServiceTag } from '../services/memory/memory.service'
31
+ import { RerankServiceTag } from '../services/memory/rerank.service'
32
+ import { MutatingApprovalServiceTag } from '../services/mutating-approval.service'
33
+ import { OrganizationMemberServiceTag } from '../services/organization-member.service'
34
+ import { OrganizationServiceTag } from '../services/organization.service'
35
+ import { PlanAgentHeartbeatServiceTag } from '../services/plan/plan-agent-heartbeat.service'
36
+ import { PlanAgentQueryServiceTag } from '../services/plan/plan-agent-query.service'
37
+ import { PlanCoordinationServiceTag } from '../services/plan/plan-coordination.service'
38
+ import { PlanCycleServiceTag } from '../services/plan/plan-cycle.service'
39
+ import { PlanDeadlineServiceTag } from '../services/plan/plan-deadline.service'
40
+ import { PlanExecutorServiceTag } from '../services/plan/plan-executor.service'
41
+ import { PlanRunServiceTag } from '../services/plan/plan-run.service'
42
+ import { PlanSchedulerServiceTag } from '../services/plan/plan-scheduler.service'
43
+ import { PlanTemplateServiceTag } from '../services/plan/plan-template.service'
44
+ import { RecentActivityTitleServiceTag } from '../services/recent-activity-title.service'
45
+ import { RecentActivityServiceTag } from '../services/recent-activity.service'
46
+ import type { makeSocialChatHistoryService } from '../services/social-chat-history.service'
47
+ import { ThreadMessageServiceTag } from '../services/thread/thread-message.service'
48
+ import { ThreadTitleServiceTag } from '../services/thread/thread-title.service'
49
+ import { isApprovalContinuationRequest as isApprovalContinuationRequestFn } from '../services/thread/thread-turn'
50
+ import type {
51
+ createThreadApprovalContinuationStream,
52
+ createThreadNativeToolApprovalStream,
53
+ createThreadTurnStream,
54
+ runThreadTurnInBackground,
55
+ ThreadTurnServiceTag,
56
+ triggerPlanNodeTurn,
57
+ } from '../services/thread/thread-turn'
58
+ import { ThreadServiceTag } from '../services/thread/thread.service'
59
+ import { UserServiceTag } from '../services/user.service'
60
+ import { GeneratedDocumentStorageServiceTag } from '../storage/generated-document-storage.service'
61
+ import { routeThreadChatMessages } from './chat-request-routing'
62
+
63
+ // eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
64
+ type SdkManagedRuntime = ManagedRuntime.ManagedRuntime<any, any>
65
+
66
+ // ── Type helpers (shared with create-runtime.ts's public interface) ──
67
+ type Svc<T extends { readonly Service: unknown }> = T['Service']
68
+ type HostSvc<T extends { readonly Service: object }> = AwaitableService<T['Service']>
69
+
70
+ export type AwaitableThreadService = HostSvc<typeof ThreadServiceTag> &
71
+ Pick<Svc<typeof ThreadServiceTag>, 'withActiveRunLease'>
72
+ export type AwaitableDocumentChunkService = HostSvc<typeof DocumentChunkServiceTag> &
73
+ Pick<Svc<typeof DocumentChunkServiceTag>, 'syncVersionedChunks'>
74
+
75
+ export type ArchiveSdkThread = (
76
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
77
+ status?: 'archived',
78
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
79
+
80
+ export type UnarchiveSdkThread = (
81
+ threadId: Parameters<Svc<typeof ThreadServiceTag>['updateStatus']>[0],
82
+ status?: 'active',
83
+ ) => ReturnType<Svc<typeof ThreadServiceTag>['updateStatus']>
84
+
85
+ export interface LotaRuntimeServices {
86
+ database: SurrealDBService
87
+ redis: RedisConnectionManager
88
+ closeRedisConnection: () => Promise<void>
89
+ agentActivityService: HostSvc<typeof AgentActivityServiceTag>
90
+ artifactService: HostSvc<typeof ArtifactServiceTag>
91
+ attachmentService: HostSvc<typeof AttachmentServiceTag>
92
+ autonomousJobService: HostSvc<typeof AutonomousJobServiceTag>
93
+ contextCompactionService: HostSvc<typeof ContextCompactionServiceTag>
94
+ documentChunkService: AwaitableDocumentChunkService
95
+ generatedDocumentStorageService: HostSvc<typeof GeneratedDocumentStorageServiceTag>
96
+ memoryService: HostSvc<typeof MemoryServiceTag>
97
+ rerankService: HostSvc<typeof RerankServiceTag>
98
+ verifyMutatingApproval: HostSvc<typeof MutatingApprovalServiceTag>
99
+ organizationService: HostSvc<typeof OrganizationServiceTag>
100
+ organizationMemberService: HostSvc<typeof OrganizationMemberServiceTag>
101
+ userService: HostSvc<typeof UserServiceTag>
102
+ recentActivityService: HostSvc<typeof RecentActivityServiceTag>
103
+ recentActivityTitleService: HostSvc<typeof RecentActivityTitleServiceTag>
104
+ socialChatHistoryService: ReturnType<typeof makeSocialChatHistoryService>
105
+ executionPlanService: HostSvc<typeof ExecutionPlanServiceTag>
106
+ planDeadlineService: HostSvc<typeof PlanDeadlineServiceTag>
107
+ planExecutorService: HostSvc<typeof PlanExecutorServiceTag>
108
+ planRunService: HostSvc<typeof PlanRunServiceTag>
109
+ planTemplateService: HostSvc<typeof PlanTemplateServiceTag>
110
+ planCoordinationService: HostSvc<typeof PlanCoordinationServiceTag>
111
+ planSchedulerService: HostSvc<typeof PlanSchedulerServiceTag>
112
+ planAgentHeartbeatService: HostSvc<typeof PlanAgentHeartbeatServiceTag>
113
+ planAgentQueryService: HostSvc<typeof PlanAgentQueryServiceTag>
114
+ planCycleService: HostSvc<typeof PlanCycleServiceTag>
115
+ threadMessageService: HostSvc<typeof ThreadMessageServiceTag>
116
+ threadService: AwaitableThreadService
117
+ threadTitleService: HostSvc<typeof ThreadTitleServiceTag>
118
+ createThreadApprovalContinuationStream: typeof createThreadApprovalContinuationStream
119
+ createThreadNativeToolApprovalStream: typeof createThreadNativeToolApprovalStream
120
+ createThreadTurnStream: typeof createThreadTurnStream
121
+ isApprovalContinuationRequest: typeof isApprovalContinuationRequestFn
122
+ runThreadTurnInBackground: typeof runThreadTurnInBackground
123
+ triggerPlanNodeTurn: typeof triggerPlanNodeTurn
124
+ }
125
+
126
+ export interface LotaRuntimeLota {
127
+ organizations: {
128
+ create: AwaitableValue<Svc<typeof OrganizationServiceTag>['createOrganization']>
129
+ upsert: AwaitableValue<Svc<typeof OrganizationServiceTag>['upsertOrganization']>
130
+ get: AwaitableValue<Svc<typeof OrganizationServiceTag>['getOrganization']>
131
+ list: AwaitableValue<Svc<typeof OrganizationServiceTag>['listOrganizations']>
132
+ update: AwaitableValue<Svc<typeof OrganizationServiceTag>['updateOrganization']>
133
+ delete: AwaitableValue<Svc<typeof OrganizationServiceTag>['deleteOrganization']>
134
+ }
135
+ users: {
136
+ upsert: AwaitableValue<Svc<typeof UserServiceTag>['upsertUser']>
137
+ get: AwaitableValue<Svc<typeof UserServiceTag>['getUser']>
138
+ list: AwaitableValue<Svc<typeof UserServiceTag>['listUsers']>
139
+ update: AwaitableValue<Svc<typeof UserServiceTag>['updateUser']>
140
+ delete: AwaitableValue<Svc<typeof UserServiceTag>['deleteUser']>
141
+ }
142
+ memberships: {
143
+ add: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['addMembership']>
144
+ listForOrganization: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['listMembershipsForOrganization']>
145
+ listForUser: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['listMembershipsForUser']>
146
+ remove: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['removeMembership']>
147
+ isMember: AwaitableValue<Svc<typeof OrganizationMemberServiceTag>['isMember']>
148
+ }
149
+ threads: {
150
+ create: AwaitableValue<Svc<typeof ThreadServiceTag>['createThread']>
151
+ list: AwaitableValue<Svc<typeof ThreadServiceTag>['listThreads']>
152
+ get: AwaitableValue<Svc<typeof ThreadServiceTag>['getThread']>
153
+ update: AwaitableValue<Svc<typeof ThreadServiceTag>['updateTitle']>
154
+ archive: AwaitableValue<ArchiveSdkThread>
155
+ unarchive: AwaitableValue<UnarchiveSdkThread>
156
+ delete: AwaitableValue<Svc<typeof ThreadServiceTag>['deleteThread']>
157
+ stop: AwaitableValue<Svc<typeof ThreadServiceTag>['stopActiveRun']>
158
+ listMessages: AwaitableValue<Svc<typeof ThreadMessageServiceTag>['listMessageHistoryPage']>
159
+ getMessage: (params: { threadId: string; messageId: string }) => Promise<ChatMessage>
160
+ sendMessage: (params: {
161
+ threadId: string
162
+ organizationId: string
163
+ userId: string
164
+ userName: string
165
+ messages: Parameters<typeof routeThreadChatMessages>[0]
166
+ }) => Promise<Awaited<ReturnType<typeof createThreadTurnStream>>>
167
+ continueApproval: (params: {
168
+ threadId: string
169
+ organizationId: string
170
+ userId: string
171
+ userName: string
172
+ messages: Parameters<typeof routeThreadChatMessages>[0]
173
+ }) => Promise<Awaited<ReturnType<typeof createThreadApprovalContinuationStream>>>
174
+ uploadAttachment: AwaitableValue<Svc<typeof AttachmentServiceTag>['uploadThreadAttachment']>
175
+ }
176
+ }
177
+
178
+ interface BuildRuntimeServiceSurfaceInput {
179
+ managedRuntime: SdkManagedRuntime
180
+ db: SurrealDBService
181
+ redisManager: RedisConnectionManager
182
+ sharedSubscriber: Svc<typeof SharedThreadStreamSubscriberTag>
183
+ threadTurnService: Svc<typeof ThreadTurnServiceTag>
184
+ socialChatHistoryService: ReturnType<typeof makeSocialChatHistoryService>
185
+ }
186
+
187
+ interface RuntimeServiceSurface {
188
+ services: LotaRuntimeServices
189
+ lota: LotaRuntimeLota
190
+ }
191
+
192
+ /**
193
+ * Eagerly resolve the full service surface exactly once.
194
+ *
195
+ * All Effect-returning service methods are wrapped with `toAwaitableService`,
196
+ * which means callers can `await` them directly while they stay discoverable
197
+ * as Effects for composition.
198
+ */
199
+ export function buildRuntimeServiceSurface(input: BuildRuntimeServiceSurfaceInput): RuntimeServiceSurface {
200
+ const { managedRuntime, db, redisManager, threadTurnService, socialChatHistoryService } = input
201
+ const runPromise = <A, E>(effect: Effect.Effect<A, E>): Promise<A> => managedRuntime.runPromise(effect)
202
+
203
+ const resolveRaw = <I, T>(tag: Context.Key<I, T>): T => managedRuntime.runSync(Effect.service(tag))
204
+ const resolveOnce = <I, T extends object>(tag: Context.Key<I, T>): AwaitableService<T> =>
205
+ toAwaitableService(resolveRaw(tag), { runPromise: (effect) => managedRuntime.runPromise(effect) })
206
+
207
+ // ── Resolve every tag eagerly. Each binding is a plain value from here. ──
208
+ const agentActivityService = resolveOnce(AgentActivityServiceTag)
209
+ const artifactService = resolveOnce(ArtifactServiceTag)
210
+ const attachmentService = resolveOnce(AttachmentServiceTag)
211
+ const autonomousJobService = resolveOnce(AutonomousJobServiceTag)
212
+ const contextCompactionService = resolveOnce(ContextCompactionServiceTag)
213
+ const documentChunkService = resolveOnce(DocumentChunkServiceTag) as AwaitableDocumentChunkService
214
+ const generatedDocumentStorageService = resolveOnce(GeneratedDocumentStorageServiceTag)
215
+ const memoryService = resolveOnce(MemoryServiceTag)
216
+ const rerankService = resolveOnce(RerankServiceTag)
217
+ const verifyMutatingApproval = resolveOnce(MutatingApprovalServiceTag)
218
+ const organizationService = resolveOnce(OrganizationServiceTag)
219
+ const organizationMemberService = resolveOnce(OrganizationMemberServiceTag)
220
+ const userService = resolveOnce(UserServiceTag)
221
+ const recentActivityService = resolveOnce(RecentActivityServiceTag)
222
+ const recentActivityTitleService = resolveOnce(RecentActivityTitleServiceTag)
223
+ const executionPlanService = resolveOnce(ExecutionPlanServiceTag)
224
+ const planDeadlineService = resolveOnce(PlanDeadlineServiceTag)
225
+ const planExecutorService = resolveOnce(PlanExecutorServiceTag)
226
+ const planRunService = resolveOnce(PlanRunServiceTag)
227
+ const planTemplateService = resolveOnce(PlanTemplateServiceTag)
228
+ const planCoordinationService = resolveOnce(PlanCoordinationServiceTag)
229
+ const planSchedulerService = resolveOnce(PlanSchedulerServiceTag)
230
+ const planAgentHeartbeatService = resolveOnce(PlanAgentHeartbeatServiceTag)
231
+ const planAgentQueryService = resolveOnce(PlanAgentQueryServiceTag)
232
+ const planCycleService = resolveOnce(PlanCycleServiceTag)
233
+ const threadMessageService = resolveOnce(ThreadMessageServiceTag)
234
+ const threadService = resolveOnce(ThreadServiceTag) as AwaitableThreadService
235
+ const threadTitleService = resolveOnce(ThreadTitleServiceTag)
236
+
237
+ // ── Resolve the raw service instances once for inline Effect composition ──
238
+ // `toAwaitableService` (used above) erases Effect return types so callers
239
+ // can `await` directly. The `getMessage`/`sendMessage`/`continueApproval`
240
+ // wrappers below need the raw Effect-returning signatures so they can
241
+ // `yield*` service methods inside their `Effect.gen` blocks.
242
+ const threadMessageServiceRaw = resolveRaw(ThreadMessageServiceTag)
243
+ const threadServiceRaw = resolveRaw(ThreadServiceTag)
244
+
245
+ const services: LotaRuntimeServices = {
246
+ database: db,
247
+ redis: redisManager,
248
+ closeRedisConnection: () => runPromise(effectTryPromise(() => redisManager.closeConnection())),
249
+ agentActivityService,
250
+ artifactService,
251
+ attachmentService,
252
+ autonomousJobService,
253
+ contextCompactionService,
254
+ documentChunkService,
255
+ generatedDocumentStorageService,
256
+ memoryService,
257
+ rerankService,
258
+ verifyMutatingApproval,
259
+ organizationService,
260
+ organizationMemberService,
261
+ userService,
262
+ recentActivityService,
263
+ recentActivityTitleService,
264
+ socialChatHistoryService,
265
+ executionPlanService,
266
+ planDeadlineService,
267
+ planExecutorService,
268
+ planRunService,
269
+ planTemplateService,
270
+ planCoordinationService,
271
+ planSchedulerService,
272
+ planAgentHeartbeatService,
273
+ planAgentQueryService,
274
+ planCycleService,
275
+ threadMessageService,
276
+ threadService,
277
+ threadTitleService,
278
+ createThreadApprovalContinuationStream: (...args) =>
279
+ runPromise(threadTurnService.createThreadApprovalContinuationStream(...args)),
280
+ createThreadNativeToolApprovalStream: (...args) =>
281
+ runPromise(threadTurnService.createThreadNativeToolApprovalStream(...args)),
282
+ createThreadTurnStream: (...args) => runPromise(threadTurnService.createThreadTurnStream(...args)),
283
+ isApprovalContinuationRequest: isApprovalContinuationRequestFn,
284
+ runThreadTurnInBackground: (...args) => runPromise(threadTurnService.runThreadTurnInBackground(...args)),
285
+ triggerPlanNodeTurn: (...args) => runPromise(threadTurnService.triggerPlanNodeTurn(...args)),
286
+ }
287
+
288
+ const lota: LotaRuntimeLota = {
289
+ organizations: {
290
+ create: (...args) => organizationService.createOrganization(...args),
291
+ upsert: (...args) => organizationService.upsertOrganization(...args),
292
+ get: (...args) => organizationService.getOrganization(...args),
293
+ list: (...args) => organizationService.listOrganizations(...args),
294
+ update: (...args) => organizationService.updateOrganization(...args),
295
+ delete: (...args) => organizationService.deleteOrganization(...args),
296
+ },
297
+ users: {
298
+ upsert: (...args) => userService.upsertUser(...args),
299
+ get: (...args) => userService.getUser(...args),
300
+ list: (...args) => userService.listUsers(...args),
301
+ update: (...args) => userService.updateUser(...args),
302
+ delete: (...args) => userService.deleteUser(...args),
303
+ },
304
+ memberships: {
305
+ add: (...args) => organizationMemberService.addMembership(...args),
306
+ listForOrganization: (...args) => organizationMemberService.listMembershipsForOrganization(...args),
307
+ listForUser: (...args) => organizationMemberService.listMembershipsForUser(...args),
308
+ remove: (...args) => organizationMemberService.removeMembership(...args),
309
+ isMember: (...args) => organizationMemberService.isMember(...args),
310
+ },
311
+ threads: {
312
+ create: (...args) => threadService.createThread(...args),
313
+ list: (...args) => threadService.listThreads(...args),
314
+ get: (...args) => threadService.getThread(...args),
315
+ update: (...args) => threadService.updateTitle(...args),
316
+ archive: (threadId, status = 'archived') => threadService.updateStatus(threadId, status),
317
+ unarchive: (threadId, status = 'active') => threadService.updateStatus(threadId, status),
318
+ delete: (...args) => threadService.deleteThread(...args),
319
+ stop: (...args) => threadService.stopActiveRun(...args),
320
+ listMessages: (...args) => threadMessageService.listMessageHistoryPage(...args),
321
+ getMessage: ({ threadId, messageId }) =>
322
+ runPromise(
323
+ Effect.gen(function* () {
324
+ const messages = yield* threadMessageServiceRaw.listMessages(ensureRecordId(threadId, TABLES.THREAD))
325
+ const message = messages.find((candidate: ChatMessage) => candidate.id === messageId)
326
+ if (!message) {
327
+ return yield* new NotFoundError({
328
+ resource: 'Thread message',
329
+ id: messageId,
330
+ message: `Thread message not found: ${messageId}`,
331
+ })
332
+ }
333
+ return message
334
+ }),
335
+ ),
336
+ sendMessage: ({ threadId, organizationId, userId, userName, messages }) =>
337
+ runPromise(
338
+ Effect.gen(function* () {
339
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
340
+ const thread = yield* threadServiceRaw.getThread(threadRef)
341
+ const routed = routeThreadChatMessages(messages)
342
+ if (routed.kind !== 'turn') {
343
+ return yield* new BadRequestError({
344
+ message: routed.kind === 'invalid' ? routed.message : 'Expected a user turn payload.',
345
+ })
346
+ }
347
+
348
+ return yield* threadTurnService.createThreadTurnStream({
349
+ thread,
350
+ threadRef,
351
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
352
+ userRef: ensureRecordId(userId, TABLES.USER),
353
+ userName,
354
+ inputMessage: routed.inputMessage,
355
+ })
356
+ }),
357
+ ),
358
+ continueApproval: ({ threadId, organizationId, userId, userName, messages }) =>
359
+ runPromise(
360
+ Effect.gen(function* () {
361
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
362
+ const thread = yield* threadServiceRaw.getThread(threadRef)
363
+ const routed = routeThreadChatMessages(messages)
364
+ if (routed.kind !== 'approval-continuation') {
365
+ return yield* new BadRequestError({
366
+ message:
367
+ routed.kind === 'invalid' ? routed.message : 'Expected approval continuation messages payload.',
368
+ })
369
+ }
370
+
371
+ return yield* threadTurnService.createThreadApprovalContinuationStream({
372
+ thread,
373
+ threadRef,
374
+ orgRef: ensureRecordId(organizationId, TABLES.ORGANIZATION),
375
+ userRef: ensureRecordId(userId, TABLES.USER),
376
+ userName,
377
+ approvalMessages: routed.approvalMessages,
378
+ })
379
+ }),
380
+ ),
381
+ uploadAttachment: (...args) => attachmentService.uploadThreadAttachment(...args),
382
+ },
383
+ }
384
+
385
+ return { services, lota }
386
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Single-slot runtime token. `createLotaRuntime()` claims the slot on success
3
+ * and releases it on disconnect, enforcing the "one runtime per process"
4
+ * invariant. Kept as a small, typed module so the guard is trivially testable
5
+ * and stays out of the main entrypoint.
6
+ */
7
+
8
+ import { Effect } from 'effect'
9
+
10
+ import { ConfigurationError } from '../effect/errors'
11
+
12
+ let activeToken: symbol | null = null
13
+
14
+ function buildConflictError(): ConfigurationError {
15
+ return new ConfigurationError({
16
+ message: 'createLotaRuntime() is process-scoped. Disconnect the active runtime before creating another one.',
17
+ })
18
+ }
19
+
20
+ /**
21
+ * Claim the process-wide runtime slot. Fails with `ConfigurationError` if it
22
+ * is already held. Preferred inside `Effect.gen` so the failure is expressed
23
+ * through the typed error channel instead of a thrown value.
24
+ */
25
+ export function acquireRuntimeTokenEffect(): Effect.Effect<symbol, ConfigurationError> {
26
+ return Effect.suspend(() => {
27
+ if (activeToken) {
28
+ return Effect.fail(buildConflictError())
29
+ }
30
+
31
+ const token = Symbol('lota-runtime')
32
+ activeToken = token
33
+ return Effect.succeed(token)
34
+ })
35
+ }
36
+
37
+ /** Release the slot only if the caller holds the current token. */
38
+ export function releaseRuntimeToken(token: symbol): void {
39
+ if (activeToken === token) {
40
+ activeToken = null
41
+ }
42
+ }
43
+
44
+ /** Diagnostic: whether a runtime is currently active in this process. */
45
+ export function isRuntimeTokenActive(): boolean {
46
+ return activeToken !== null
47
+ }
@@ -46,13 +46,13 @@ export function withLoggedSocialToolSet(
46
46
  execute: (...args: unknown[]): Promise<unknown> =>
47
47
  Effect.runPromise(
48
48
  Effect.gen(function* () {
49
- aiLogger.info`Slack social-chat tool start: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
49
+ aiLogger.debug`Slack social-chat tool start: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
50
50
  const result: unknown = yield* Effect.tryPromise({
51
51
  try: () => executableTool.execute(...args),
52
52
  catch: (error: unknown) => new SocialChatAgentRunnerError({ message: String(error), cause: error }),
53
53
  })
54
54
  params.executedToolNames.push(toolName)
55
- aiLogger.info`Slack social-chat tool finish: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
55
+ aiLogger.debug`Slack social-chat tool finish: agentId=${params.agentId}, tool=${toolName}, channelId=${params.channelId}, threadId=${params.threadId}`
56
56
  return result
57
57
  }).pipe(
58
58
  Effect.tapError((error) =>
@@ -117,9 +117,11 @@ function runSocialAgentTurnEffect(params: {
117
117
  extraInstructions: runtimeConfig.extraInstructions,
118
118
  stopWhen: [stepCountIs(runtimeConfig.maxSteps)],
119
119
  })
120
- const response = yield* Effect.promise(() =>
121
- agent.generate({ prompt: params.prompt, abortSignal: params.abortSignal }),
122
- )
120
+ const response = yield* Effect.tryPromise({
121
+ try: () => agent.generate({ prompt: params.prompt, abortSignal: params.abortSignal }),
122
+ catch: (cause) =>
123
+ new SocialChatAgentRunnerError({ message: `Social chat agent ${params.agentId} generate() failed.`, cause }),
124
+ })
123
125
  const text = response.text.trim()
124
126
  if (!text) {
125
127
  return yield* new SocialChatAgentRunnerError({
@@ -2,6 +2,7 @@ import { stripSlackToolExecutionNoticeMarkdown } from '@lota-sdk/shared'
2
2
  import type { Message, Thread } from 'chat'
3
3
  import { Schema, Effect } from 'effect'
4
4
 
5
+ import { iterateEffect } from '../../effect/helpers'
5
6
  import type {
6
7
  SocialChatHistoryMessage,
7
8
  SocialChatHistoryMetadata,
@@ -107,21 +108,29 @@ export function collectThreadMessages(thread: Thread, incomingMessage: Message):
107
108
  return Effect.runPromise(
108
109
  Effect.catch(
109
110
  Effect.gen(function* () {
110
- const collected: Message[] = []
111
111
  const iterator = thread.allMessages[Symbol.asyncIterator]()
112
112
 
113
- for (;;) {
114
- const next = yield* Effect.tryPromise({
115
- try: () => iterator.next(),
116
- catch: (error: unknown) =>
117
- new SocialChatHistoryError({ message: 'Failed to read thread messages', cause: error }),
118
- })
119
- if (next.done) break
120
- collected.push(next.value)
121
- }
113
+ const collected = yield* iterateEffect<{ messages: Message[]; done: boolean }, SocialChatHistoryError, never>(
114
+ { messages: [], done: false },
115
+ {
116
+ while: (state) => !state.done,
117
+ body: (state) =>
118
+ Effect.tryPromise({
119
+ try: () => iterator.next(),
120
+ catch: (error: unknown) =>
121
+ new SocialChatHistoryError({ message: 'Failed to read thread messages', cause: error }),
122
+ }).pipe(
123
+ Effect.map((next) =>
124
+ next.done
125
+ ? { messages: state.messages, done: true }
126
+ : { messages: [...state.messages, next.value], done: false },
127
+ ),
128
+ ),
129
+ },
130
+ )
122
131
 
123
- return collected.length > 0
124
- ? collected
132
+ return collected.messages.length > 0
133
+ ? collected.messages
125
134
  : thread.recentMessages.length > 0
126
135
  ? [...thread.recentMessages]
127
136
  : [incomingMessage]