@lota-sdk/core 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/package.json +2 -2
  2. package/src/ai/embedding-cache.ts +3 -1
  3. package/src/ai-gateway/ai-gateway.ts +164 -82
  4. package/src/ai-gateway/index.ts +16 -1
  5. package/src/config/agent-defaults.ts +4 -107
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/background-processing.ts +1 -1
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +22 -25
  10. package/src/config/thread-defaults.ts +1 -10
  11. package/src/create-runtime.ts +145 -670
  12. package/src/db/base.service.ts +30 -38
  13. package/src/db/memory-query-builder.ts +2 -1
  14. package/src/db/memory-store.ts +29 -20
  15. package/src/db/memory.ts +188 -195
  16. package/src/db/service-normalization.ts +97 -64
  17. package/src/db/service.ts +496 -384
  18. package/src/db/startup.ts +30 -19
  19. package/src/effect/helpers.ts +30 -5
  20. package/src/effect/index.ts +7 -7
  21. package/src/effect/layers.ts +75 -72
  22. package/src/effect/services.ts +15 -11
  23. package/src/embeddings/provider.ts +65 -71
  24. package/src/index.ts +13 -12
  25. package/src/queues/autonomous-job.queue.ts +177 -143
  26. package/src/queues/context-compaction.queue.ts +41 -39
  27. package/src/queues/delayed-node-promotion.queue.ts +61 -42
  28. package/src/queues/document-processor.queue.ts +5 -3
  29. package/src/queues/index.ts +1 -0
  30. package/src/queues/memory-consolidation.queue.ts +79 -53
  31. package/src/queues/organization-learning.queue.ts +70 -33
  32. package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
  33. package/src/queues/plan-scheduler.queue.ts +101 -97
  34. package/src/queues/post-chat-memory.queue.ts +56 -46
  35. package/src/queues/queue-factory.ts +146 -69
  36. package/src/queues/queues.service.ts +61 -0
  37. package/src/queues/title-generation.queue.ts +44 -44
  38. package/src/redis/connection.ts +181 -164
  39. package/src/redis/org-memory-lock.ts +24 -9
  40. package/src/redis/redis-lease-lock.ts +8 -1
  41. package/src/redis/stream-context.ts +17 -9
  42. package/src/runtime/agent-identity-overrides.ts +7 -3
  43. package/src/runtime/agent-runtime-policy.ts +10 -5
  44. package/src/runtime/agent-stream-helpers.ts +24 -15
  45. package/src/runtime/chat-run-orchestration.ts +1 -1
  46. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  47. package/src/runtime/context-compaction/context-compaction.ts +131 -85
  48. package/src/runtime/domain-layer.ts +203 -0
  49. package/src/runtime/execution-plan-visibility.ts +5 -2
  50. package/src/runtime/graph-designer.ts +0 -14
  51. package/src/runtime/helper-model.ts +8 -4
  52. package/src/runtime/index.ts +1 -1
  53. package/src/runtime/indexed-repositories-policy.ts +2 -6
  54. package/src/runtime/memory/memory-block.ts +19 -9
  55. package/src/runtime/memory/memory-pipeline.ts +53 -66
  56. package/src/runtime/memory/memory-scope.ts +33 -29
  57. package/src/runtime/plugin-resolution.ts +58 -62
  58. package/src/runtime/post-turn-side-effects.ts +139 -161
  59. package/src/runtime/retrieval-adapters.ts +4 -4
  60. package/src/runtime/runtime-config.ts +3 -9
  61. package/src/runtime/runtime-extensions.ts +0 -43
  62. package/src/runtime/runtime-lifecycle.ts +124 -0
  63. package/src/runtime/runtime-services.ts +455 -0
  64. package/src/runtime/runtime-worker-registry.ts +113 -30
  65. package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
  66. package/src/runtime/social-chat/social-chat-history.ts +24 -13
  67. package/src/runtime/social-chat/social-chat.ts +420 -369
  68. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
  69. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  70. package/src/runtime/thread-chat-helpers.ts +18 -9
  71. package/src/runtime/thread-turn-context.ts +28 -74
  72. package/src/runtime/turn-lifecycle.ts +6 -14
  73. package/src/services/agent-activity.service.ts +169 -176
  74. package/src/services/agent-executor.service.ts +207 -196
  75. package/src/services/artifact.service.ts +10 -5
  76. package/src/services/attachment.service.ts +16 -48
  77. package/src/services/autonomous-job.service.ts +81 -87
  78. package/src/services/background-work.service.ts +54 -0
  79. package/src/services/chat-run-registry.service.ts +3 -1
  80. package/src/services/context-compaction.service.ts +8 -10
  81. package/src/services/document-chunk.service.ts +8 -17
  82. package/src/services/execution-plan/execution-plan-graph.ts +122 -109
  83. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  84. package/src/services/execution-plan/execution-plan.service.ts +68 -51
  85. package/src/services/feedback-loop.service.ts +1 -1
  86. package/src/services/global-orchestrator.service.ts +49 -15
  87. package/src/services/graph-full-routing.ts +49 -37
  88. package/src/services/index.ts +1 -0
  89. package/src/services/institutional-memory.service.ts +8 -17
  90. package/src/services/learned-skill.service.ts +38 -35
  91. package/src/services/memory/memory-conversation.ts +10 -5
  92. package/src/services/memory/memory-errors.ts +27 -0
  93. package/src/services/memory/memory-org-memory.ts +14 -3
  94. package/src/services/memory/memory-preseeded.ts +10 -4
  95. package/src/services/memory/memory-utils.ts +2 -1
  96. package/src/services/memory/memory.service.ts +37 -52
  97. package/src/services/memory/rerank.service.ts +3 -11
  98. package/src/services/monitoring-window.service.ts +1 -1
  99. package/src/services/mutating-approval.service.ts +1 -1
  100. package/src/services/node-workspace.service.ts +2 -2
  101. package/src/services/notification.service.ts +16 -4
  102. package/src/services/organization-member.service.ts +1 -1
  103. package/src/services/organization.service.ts +34 -51
  104. package/src/services/ownership-dispatcher.service.ts +148 -95
  105. package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
  106. package/src/services/plan/plan-agent-query.service.ts +13 -9
  107. package/src/services/plan/plan-approval.service.ts +52 -48
  108. package/src/services/plan/plan-artifact.service.ts +2 -2
  109. package/src/services/plan/plan-builder.service.ts +2 -2
  110. package/src/services/plan/plan-checkpoint.service.ts +1 -1
  111. package/src/services/plan/plan-compiler.service.ts +1 -1
  112. package/src/services/plan/plan-completion-side-effects.ts +99 -113
  113. package/src/services/plan/plan-coordination.service.ts +1 -1
  114. package/src/services/plan/plan-cycle.service.ts +171 -202
  115. package/src/services/plan/plan-deadline.service.ts +304 -307
  116. package/src/services/plan/plan-event-delivery.service.ts +84 -72
  117. package/src/services/plan/plan-executor-context.ts +2 -0
  118. package/src/services/plan/plan-executor-graph.ts +375 -353
  119. package/src/services/plan/plan-executor-helpers.ts +60 -75
  120. package/src/services/plan/plan-executor.service.ts +494 -489
  121. package/src/services/plan/plan-run.service.ts +12 -19
  122. package/src/services/plan/plan-scheduler.service.ts +89 -82
  123. package/src/services/plan/plan-template.service.ts +1 -1
  124. package/src/services/plan/plan-transaction-events.ts +8 -5
  125. package/src/services/plan/plan-validator.service.ts +1 -1
  126. package/src/services/plan/plan-workspace.service.ts +17 -11
  127. package/src/services/plugin-executor.service.ts +26 -21
  128. package/src/services/quality-metrics.service.ts +1 -1
  129. package/src/services/queue-job.service.ts +8 -17
  130. package/src/services/recent-activity-title.service.ts +22 -10
  131. package/src/services/recent-activity.service.ts +1 -1
  132. package/src/services/skill-resolver.service.ts +1 -1
  133. package/src/services/social-chat-history.service.ts +37 -20
  134. package/src/services/system-executor.service.ts +25 -20
  135. package/src/services/thread/thread-bootstrap.ts +37 -19
  136. package/src/services/thread/thread-listing.ts +2 -1
  137. package/src/services/thread/thread-memory-block.ts +18 -5
  138. package/src/services/thread/thread-message.service.ts +30 -13
  139. package/src/services/thread/thread-title.service.ts +1 -1
  140. package/src/services/thread/thread-turn-execution.ts +87 -83
  141. package/src/services/thread/thread-turn-preparation.service.ts +65 -40
  142. package/src/services/thread/thread-turn-streaming.ts +32 -36
  143. package/src/services/thread/thread-turn.ts +43 -29
  144. package/src/services/thread/thread.service.ts +32 -8
  145. package/src/services/user.service.ts +1 -1
  146. package/src/services/write-intent-validator.service.ts +1 -1
  147. package/src/storage/attachment-storage.service.ts +7 -4
  148. package/src/storage/generated-document-storage.service.ts +1 -1
  149. package/src/system-agents/context-compaction.agent.ts +1 -1
  150. package/src/system-agents/helper-agent-options.ts +1 -1
  151. package/src/system-agents/memory-reranker.agent.ts +1 -1
  152. package/src/system-agents/memory.agent.ts +1 -1
  153. package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
  154. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  155. package/src/system-agents/skill-extractor.agent.ts +1 -1
  156. package/src/system-agents/skill-manager.agent.ts +1 -1
  157. package/src/system-agents/thread-router.agent.ts +23 -20
  158. package/src/system-agents/title-generator.agent.ts +1 -1
  159. package/src/tools/execution-plan.tool.ts +36 -20
  160. package/src/tools/fetch-webpage.tool.ts +30 -22
  161. package/src/tools/firecrawl-client.ts +1 -6
  162. package/src/tools/plan-approval.tool.ts +9 -1
  163. package/src/tools/remember-memory.tool.ts +3 -6
  164. package/src/tools/research-topic.tool.ts +12 -3
  165. package/src/tools/search-web.tool.ts +26 -18
  166. package/src/tools/search.tool.ts +4 -5
  167. package/src/tools/team-think.tool.ts +139 -121
  168. package/src/utils/async.ts +15 -6
  169. package/src/utils/errors.ts +27 -15
  170. package/src/workers/bootstrap.ts +34 -58
  171. package/src/workers/memory-consolidation.worker.ts +4 -1
  172. package/src/workers/organization-learning.worker.ts +16 -3
  173. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  174. package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
  175. package/src/workers/skill-extraction.runner.ts +13 -15
  176. package/src/workers/worker-utils.ts +14 -8
  177. package/src/config/search.ts +0 -3
  178. package/src/effect/awaitable-effect.ts +0 -87
  179. package/src/effect/runtime-ref.ts +0 -25
  180. package/src/effect/runtime.ts +0 -31
  181. package/src/redis/runtime-connection.ts +0 -10
  182. package/src/runtime/agent-types.ts +0 -1
@@ -1,8 +1,10 @@
1
1
  import type { PlanExecutionVisibility, PlanNodeSpecRecord, PlanSpecRecord } from '@lota-sdk/shared'
2
2
 
3
+ import type { ResolvedAgentConfig } from '../config/agent-defaults'
3
4
  import { isAgentName } from '../config/agent-defaults'
4
5
 
5
6
  export function resolvePlanNodeExecutionVisibility(
7
+ agentConfig: ResolvedAgentConfig,
6
8
  plan: Pick<PlanSpecRecord, 'defaultExecutionVisibility'>,
7
9
  node: Pick<PlanNodeSpecRecord, 'executionVisibility' | 'owner'>,
8
10
  ): PlanExecutionVisibility {
@@ -12,12 +14,13 @@ export function resolvePlanNodeExecutionVisibility(
12
14
  return configuredVisibility
13
15
  }
14
16
 
15
- return node.owner.executorType === 'agent' && isAgentName(node.owner.ref) ? 'visible' : 'silent'
17
+ return node.owner.executorType === 'agent' && isAgentName(agentConfig, node.owner.ref) ? 'visible' : 'silent'
16
18
  }
17
19
 
18
20
  export function shouldPlanNodeUseVisibleTurn(
21
+ agentConfig: ResolvedAgentConfig,
19
22
  plan: Pick<PlanSpecRecord, 'defaultExecutionVisibility'>,
20
23
  node: Pick<PlanNodeSpecRecord, 'executionVisibility' | 'owner'>,
21
24
  ): boolean {
22
- return resolvePlanNodeExecutionVisibility(plan, node) === 'visible'
25
+ return resolvePlanNodeExecutionVisibility(agentConfig, plan, node) === 'visible'
23
26
  }
@@ -1,19 +1,5 @@
1
1
  import type { GraphDesignRequest, GraphDesignResponse } from '@lota-sdk/shared'
2
- import { Effect } from 'effect'
3
-
4
- import { getOptionalCurrentRuntime } from '../effect/runtime-ref'
5
- import { RuntimeConfigServiceTag } from '../effect/services'
6
2
 
7
3
  export interface GraphDesigner {
8
4
  designGraph(request: GraphDesignRequest): Promise<GraphDesignResponse>
9
5
  }
10
-
11
- export function getGraphDesigner(): GraphDesigner | null {
12
- const runtime = getOptionalCurrentRuntime()
13
- if (!runtime) {
14
- return null
15
- }
16
-
17
- const runtimeConfig = runtime.runSync(Effect.service(RuntimeConfigServiceTag))
18
- return runtimeConfig.graphDesigner ?? null
19
- }
@@ -253,7 +253,7 @@ export function createHelperModelRuntime() {
253
253
  }
254
254
 
255
255
  function generateHelperText(params: GenerateHelperTextParams): Promise<string> {
256
- return Effect.runPromise(generateHelperTextEffect(params))
256
+ return Effect.runPromise(generateHelperTextEffect(params).pipe(Effect.withSpan('HelperModel.generateHelperText')))
257
257
  }
258
258
 
259
259
  function generateHelperStructuredEffect<T>(
@@ -300,7 +300,9 @@ export function createHelperModelRuntime() {
300
300
  }
301
301
 
302
302
  function generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T> {
303
- return Effect.runPromise(generateHelperStructuredEffect(params))
303
+ return Effect.runPromise(
304
+ generateHelperStructuredEffect(params).pipe(Effect.withSpan('HelperModel.generateHelperStructured')),
305
+ )
304
306
  }
305
307
 
306
308
  return { generateHelperTextEffect, generateHelperText, generateHelperStructuredEffect, generateHelperStructured }
@@ -308,6 +310,8 @@ export function createHelperModelRuntime() {
308
310
 
309
311
  export type HelperModelRuntime = ReturnType<typeof createHelperModelRuntime>
310
312
 
311
- export class HelperModelTag extends Context.Service<HelperModelTag, HelperModelRuntime>()('HelperModel') {}
313
+ export class HelperModelTag extends Context.Service<HelperModelTag, HelperModelRuntime>()(
314
+ '@lota-sdk/core/HelperModel',
315
+ ) {}
312
316
 
313
- export const HelperModelLive = Layer.succeed(HelperModelTag, createHelperModelRuntime())
317
+ export const HelperModelLive = Layer.sync(HelperModelTag, () => createHelperModelRuntime())
@@ -1,8 +1,8 @@
1
1
  export * from './approval-continuation'
2
2
  export * from './agent-runtime-policy'
3
3
  export * from './agent-stream-helpers'
4
- export * from './agent-types'
5
4
  export * from './chat-request-routing'
5
+ export * from './chat-run-orchestration'
6
6
  export * from './chat-run-registry'
7
7
  export * from './context-compaction/context-compaction'
8
8
  export * from './execution-plan'
@@ -1,5 +1,3 @@
1
- import { getAgentRoster } from '../config/agent-defaults'
2
-
3
1
  export type IndexedRepoAgentName = string
4
2
 
5
3
  export const REPO_SECTION_NAMES = [
@@ -16,13 +14,11 @@ export type RepoSectionName = (typeof REPO_SECTION_NAMES)[number]
16
14
  const ALL_REPO_SECTIONS: RepoSectionName[] = [...REPO_SECTION_NAMES]
17
15
 
18
16
  export function buildDefaultRepoSectionsByAgent(
19
- roster: readonly IndexedRepoAgentName[] = getAgentRoster(),
17
+ roster: readonly IndexedRepoAgentName[],
20
18
  ): Record<IndexedRepoAgentName, RepoSectionName[]> {
21
19
  return Object.fromEntries(roster.map((agentId) => [agentId, [...ALL_REPO_SECTIONS]]))
22
20
  }
23
21
 
24
- export function emptyAgentContextMap(
25
- roster: readonly IndexedRepoAgentName[] = getAgentRoster(),
26
- ): Record<IndexedRepoAgentName, string> {
22
+ export function emptyAgentContextMap(roster: readonly IndexedRepoAgentName[]): Record<IndexedRepoAgentName, string> {
27
23
  return Object.fromEntries(roster.map((agentId) => [agentId, '']))
28
24
  }
@@ -1,10 +1,14 @@
1
- import type { Cause } from 'effect'
2
- import { DateTime, Effect } from 'effect'
1
+ import { Effect, Schema } from 'effect'
3
2
  import { z } from 'zod'
4
3
 
5
- import { effectTryPromise } from '../../effect/helpers'
4
+ import { nowDate } from '../../utils/date-time'
6
5
  import { compactWhitespace } from '../../utils/string'
7
6
 
7
+ export class MemoryBlockCompactError extends Schema.TaggedErrorClass<MemoryBlockCompactError>()(
8
+ 'MemoryBlockCompactError',
9
+ { message: Schema.String, cause: Schema.Defect },
10
+ ) {}
11
+
8
12
  function escapeRegex(value: string): string {
9
13
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
10
14
  }
@@ -61,7 +65,7 @@ export interface CompactMemoryBlockEntriesParams {
61
65
  compact: (params: {
62
66
  previousSummary: string
63
67
  newEntriesText: string
64
- }) => PromiseLike<string> | Effect.Effect<string, unknown>
68
+ }) => PromiseLike<string> | Effect.Effect<string, MemoryBlockCompactError>
65
69
  }
66
70
 
67
71
  export interface CompactMemoryBlockEntriesResult {
@@ -106,7 +110,7 @@ function toMemoryBlockEntriesSource(entries: Array<{ role: string; content: stri
106
110
 
107
111
  export function compactMemoryBlockEntries(
108
112
  params: CompactMemoryBlockEntriesParams,
109
- ): Effect.Effect<CompactMemoryBlockEntriesResult, Cause.UnknownError> {
113
+ ): Effect.Effect<CompactMemoryBlockEntriesResult, MemoryBlockCompactError> {
110
114
  return Effect.gen(function* () {
111
115
  let summary = typeof params.previousSummary === 'string' ? params.previousSummary.trim() : ''
112
116
  let entries = [...params.entries]
@@ -114,9 +118,15 @@ export function compactMemoryBlockEntries(
114
118
 
115
119
  while (entries.length >= params.triggerEntries) {
116
120
  const chunk = entries.slice(0, params.chunkEntries)
117
- const nextSummary = (yield* effectTryPromise(() =>
118
- params.compact({ previousSummary: summary, newEntriesText: toMemoryBlockEntriesSource(chunk) }),
119
- )).trim()
121
+ const compactParams = { previousSummary: summary, newEntriesText: toMemoryBlockEntriesSource(chunk) }
122
+ const value = params.compact(compactParams)
123
+ const raw = yield* Effect.isEffect(value)
124
+ ? value
125
+ : Effect.tryPromise({
126
+ try: () => Promise.resolve(value),
127
+ catch: (cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause }),
128
+ })
129
+ const nextSummary = raw.trim()
120
130
 
121
131
  if (!nextSummary) {
122
132
  break
@@ -162,7 +172,7 @@ export interface CreateMemoryBlockRuntimeOptions {
162
172
  export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOptions = {}): MemoryBlockRuntime {
163
173
  const labelPrefixRegex = createLabelPrefixRegex(options.labelRoles ?? [])
164
174
  const memoryBlockMaxChars = options.maxChars ?? 400
165
- const now = options.now ?? (() => Effect.runSync(DateTime.nowAsDate))
175
+ const now = options.now ?? nowDate
166
176
 
167
177
  const normalizeMemoryBlockEntry = (entry: string): string => {
168
178
  const normalizedLines = entry
@@ -1,3 +1,5 @@
1
+ import { Match } from 'effect'
2
+
1
3
  import { clampImportance, compactWhitespace } from '../../utils/string'
2
4
 
3
5
  const SCORE_WEIGHTS = {
@@ -123,20 +125,17 @@ function scoreFact<T extends MemoryFactInput>(fact: T): number {
123
125
  const durability = fact.durability ?? 'standard'
124
126
  const type = fact.type ?? 'fact'
125
127
 
126
- const durabilityWeight =
127
- durability === 'core'
128
- ? SCORE_WEIGHTS.durability.core
129
- : durability === 'standard'
130
- ? SCORE_WEIGHTS.durability.standard
131
- : SCORE_WEIGHTS.durability.weak
132
- const typeWeight =
133
- type === 'decision'
134
- ? SCORE_WEIGHTS.type.decision
135
- : type === 'fact'
136
- ? SCORE_WEIGHTS.type.fact
137
- : type === 'preference'
138
- ? SCORE_WEIGHTS.type.preference
139
- : SCORE_WEIGHTS.type.default
128
+ const durabilityWeight = Match.value(durability).pipe(
129
+ Match.when('core', () => SCORE_WEIGHTS.durability.core),
130
+ Match.when('standard', () => SCORE_WEIGHTS.durability.standard),
131
+ Match.orElse(() => SCORE_WEIGHTS.durability.weak),
132
+ )
133
+ const typeWeight = Match.value(type).pipe(
134
+ Match.when('decision', () => SCORE_WEIGHTS.type.decision),
135
+ Match.when('fact', () => SCORE_WEIGHTS.type.fact),
136
+ Match.when('preference', () => SCORE_WEIGHTS.type.preference),
137
+ Match.orElse(() => SCORE_WEIGHTS.type.default),
138
+ )
140
139
  const lengthWeight =
141
140
  Math.min(fact.content.length, SCORE_WEIGHTS.maxContentLength) / SCORE_WEIGHTS.maxContentLength / 10
142
141
 
@@ -340,61 +339,49 @@ export function compileMemoryUpdatesFromDelta(params: {
340
339
  deleteSet.add(invalidateId)
341
340
  }
342
341
 
343
- if (item.classification === 'duplicate') {
344
- const targetId = item.targetMemoryIds[0]
345
- if (targetId) {
346
- updates.push({ id: targetId, text: existingById.get(targetId) ?? item.fact, event: 'NONE' })
347
- } else {
348
- updates.push({ id: `noop_${item.index}`, text: item.fact, event: 'NONE' })
349
- }
350
- continue
351
- }
352
-
353
- if (item.classification === 'enriches') {
354
- const targetId = item.targetMemoryIds[0]
355
- if (targetId) {
356
- const relations = buildRelations(item, 'new').filter((relation) => relation.memoryId !== targetId)
357
- updates.push({
358
- id: targetId,
359
- text: item.fact,
360
- event: 'UPDATE',
361
- oldMemory: existingById.get(targetId),
362
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
363
- })
364
- continue
365
- }
342
+ const nextUpdates = Match.value(item.classification).pipe(
343
+ Match.when('duplicate', (): MemoryUpdateItemLike[] => {
344
+ const targetId = item.targetMemoryIds[0]
345
+ if (targetId) {
346
+ return [{ id: targetId, text: existingById.get(targetId) ?? item.fact, event: 'NONE' }]
347
+ }
348
+ return [{ id: `noop_${item.index}`, text: item.fact, event: 'NONE' }]
349
+ }),
350
+ Match.when('enriches', (): MemoryUpdateItemLike[] => {
351
+ const targetId = item.targetMemoryIds[0]
352
+ if (targetId) {
353
+ const relations = buildRelations(item, 'new').filter((relation) => relation.memoryId !== targetId)
354
+ return [
355
+ {
356
+ id: targetId,
357
+ text: item.fact,
358
+ event: 'UPDATE',
359
+ oldMemory: existingById.get(targetId),
360
+ ...(relations.length > 0 ? { relatesTo: relations } : {}),
361
+ },
362
+ ]
363
+ }
366
364
 
367
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
368
- const relations = buildRelations(item, 'new')
369
- updates.push({
370
- id: addId,
371
- text: item.fact,
372
- event: 'ADD',
373
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
374
- })
375
- continue
376
- }
365
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
366
+ const relations = buildRelations(item, 'new')
367
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
368
+ }),
369
+ Match.whenOr('supersedes', 'contradicts', (classification): MemoryUpdateItemLike[] => {
370
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
371
+ const relations = buildRelations(item, classification)
372
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
373
+ }),
374
+ Match.when('new', (): MemoryUpdateItemLike[] => {
375
+ const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
376
+ const relations = buildRelations(item, 'new')
377
+ return [{ id: addId, text: item.fact, event: 'ADD', ...(relations.length > 0 ? { relatesTo: relations } : {}) }]
378
+ }),
379
+ Match.exhaustive,
380
+ )
377
381
 
378
- if (item.classification === 'supersedes' || item.classification === 'contradicts') {
379
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
380
- const relations = buildRelations(item, item.classification)
381
- updates.push({
382
- id: addId,
383
- text: item.fact,
384
- event: 'ADD',
385
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
386
- })
387
- continue
382
+ for (const update of nextUpdates) {
383
+ updates.push(update)
388
384
  }
389
-
390
- const addId = addIdByFactIndex.get(item.index) ?? nextAddId()
391
- const relations = buildRelations(item, 'new')
392
- updates.push({
393
- id: addId,
394
- text: item.fact,
395
- event: 'ADD',
396
- ...(relations.length > 0 ? { relatesTo: relations } : {}),
397
- })
398
385
  }
399
386
 
400
387
  const preservedRelationTargetIds = new Set<string>()
@@ -1,4 +1,4 @@
1
- import { Schema } from 'effect'
1
+ import { Effect, Schema } from 'effect'
2
2
 
3
3
  export const ORG_SCOPE_PREFIX = 'org'
4
4
 
@@ -9,41 +9,45 @@ export class MemoryScopeError extends Schema.TaggedErrorClass<MemoryScopeError>(
9
9
  message: Schema.String,
10
10
  }) {}
11
11
 
12
- function stripRecordPrefix(id: string): string {
12
+ function stripRecordPrefix(id: string): Effect.Effect<string, MemoryScopeError> {
13
13
  if (typeof id !== 'string' || id.length === 0) {
14
- throw new MemoryScopeError({ message: 'id must be a non-empty string' })
14
+ return Effect.fail(new MemoryScopeError({ message: 'id must be a non-empty string' }))
15
15
  }
16
16
  const [, ...rest] = id.split(':')
17
- return rest.length > 0 ? rest.join(':') : id
17
+ return Effect.succeed(rest.length > 0 ? rest.join(':') : id)
18
18
  }
19
19
 
20
- export function scopeId(prefix: string, id: string): string {
21
- if (typeof prefix !== 'string' || prefix.length === 0) {
22
- throw new MemoryScopeError({ message: 'prefix must be a non-empty string' })
23
- }
24
- if (!SCOPE_PREFIX_REGEX.test(prefix)) {
25
- throw new MemoryScopeError({ message: `Invalid scope prefix: ${prefix}` })
26
- }
27
-
28
- const stripped = stripRecordPrefix(id)
29
- const scoped = `${prefix}:${stripped}`
30
- if (scoped.length > SCOPE_ID_MAX_LENGTH) {
31
- throw new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
32
- }
33
-
34
- return scoped
20
+ export function scopeId(prefix: string, id: string): Effect.Effect<string, MemoryScopeError> {
21
+ return Effect.gen(function* () {
22
+ if (typeof prefix !== 'string' || prefix.length === 0) {
23
+ return yield* new MemoryScopeError({ message: 'prefix must be a non-empty string' })
24
+ }
25
+ if (!SCOPE_PREFIX_REGEX.test(prefix)) {
26
+ return yield* new MemoryScopeError({ message: `Invalid scope prefix: ${prefix}` })
27
+ }
28
+
29
+ const stripped = yield* stripRecordPrefix(id)
30
+ const scoped = `${prefix}:${stripped}`
31
+ if (scoped.length > SCOPE_ID_MAX_LENGTH) {
32
+ return yield* new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
33
+ }
34
+
35
+ return scoped
36
+ })
35
37
  }
36
38
 
37
- export function agentScopeId(orgId: string, agentName: string): string {
38
- if (typeof agentName !== 'string' || agentName.trim().length === 0) {
39
- throw new MemoryScopeError({ message: 'agentName must be a non-empty string' })
40
- }
41
- const strippedOrgId = stripRecordPrefix(orgId)
42
- const scoped = `agent:${strippedOrgId}:${agentName.trim()}`
39
+ export function agentScopeId(orgId: string, agentName: string): Effect.Effect<string, MemoryScopeError> {
40
+ return Effect.gen(function* () {
41
+ if (typeof agentName !== 'string' || agentName.trim().length === 0) {
42
+ return yield* new MemoryScopeError({ message: 'agentName must be a non-empty string' })
43
+ }
44
+ const strippedOrgId = yield* stripRecordPrefix(orgId)
45
+ const scoped = `agent:${strippedOrgId}:${agentName.trim()}`
43
46
 
44
- if (scoped.length > SCOPE_ID_MAX_LENGTH) {
45
- throw new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
46
- }
47
+ if (scoped.length > SCOPE_ID_MAX_LENGTH) {
48
+ return yield* new MemoryScopeError({ message: 'scopeId exceeds maximum length' })
49
+ }
47
50
 
48
- return scoped
51
+ return scoped
52
+ })
49
53
  }
@@ -1,9 +1,7 @@
1
1
  import { Schema, Effect } from 'effect'
2
2
 
3
- import { getPluginRuntime } from '../config/agent-defaults'
4
3
  import type { RecordIdRef } from '../db/record-id'
5
- import type { LotaRuntimeIndexedRepositoriesContext } from './runtime-extensions'
6
- import { getRuntimeAdapters } from './runtime-extensions'
4
+ import type { LotaRuntimeAdapters, LotaRuntimeIndexedRepositoriesContext } from './runtime-extensions'
7
5
 
8
6
  function isRecord(value: unknown): value is Record<string, unknown> {
9
7
  return typeof value === 'object' && value !== null
@@ -35,79 +33,68 @@ function readFunctionEffect(
35
33
  return Effect.succeed(value as (...args: unknown[]) => PromiseLike<unknown>)
36
34
  }
37
35
 
36
+ function configError(message: string): PluginResolutionError {
37
+ return new PluginResolutionError({ stage: 'configuration', message, cause: undefined })
38
+ }
39
+
40
+ function requireRecord(value: unknown, message: string): Effect.Effect<Record<string, unknown>, PluginResolutionError> {
41
+ return isRecord(value) ? Effect.succeed(value) : Effect.fail(configError(message))
42
+ }
43
+
38
44
  function resolvePluginServiceEffect(
45
+ pluginRuntime: Record<string, unknown> | undefined,
39
46
  pluginName: string,
40
47
  serviceName: string,
41
48
  methodName: string,
42
49
  ): Effect.Effect<(...args: unknown[]) => PromiseLike<unknown>, PluginResolutionError> {
43
- const pluginRuntime = getPluginRuntime()
44
- if (!pluginRuntime) {
45
- return Effect.fail(
46
- new PluginResolutionError({
47
- stage: 'configuration',
48
- message: `Plugin runtime is not configured. Missing "${pluginName}" integration.`,
49
- cause: undefined,
50
- }),
51
- )
52
- }
53
-
54
- const plugin = pluginRuntime[pluginName]
55
- if (!isRecord(plugin)) {
56
- return Effect.fail(
57
- new PluginResolutionError({
58
- stage: 'configuration',
59
- message: `Plugin "${pluginName}" is not configured in the current runtime.`,
60
- cause: undefined,
61
- }),
62
- )
63
- }
64
-
65
- const services = plugin.services
66
- if (!isRecord(services)) {
67
- return Effect.fail(
68
- new PluginResolutionError({
69
- stage: 'configuration',
70
- message: `Plugin "${pluginName}" does not expose a services registry.`,
71
- cause: undefined,
72
- }),
73
- )
74
- }
75
-
76
- const service = services[serviceName]
77
- if (!isRecord(service)) {
78
- return Effect.fail(
79
- new PluginResolutionError({
80
- stage: 'configuration',
81
- message: `Plugin "${pluginName}" service "${serviceName}" is not configured.`,
82
- cause: undefined,
83
- }),
84
- )
85
- }
86
-
87
- return readFunctionEffect(
88
- service[methodName],
89
- `Plugin "${pluginName}" service "${serviceName}" is missing method "${methodName}".`,
90
- ).pipe(
91
- Effect.map(
92
- (method) =>
93
- (...args: unknown[]) =>
94
- Reflect.apply(method, service, args),
50
+ return Effect.fromNullishOr(pluginRuntime).pipe(
51
+ Effect.mapError(() => configError(`Plugin runtime is not configured. Missing "${pluginName}" integration.`)),
52
+ Effect.flatMap((runtime) =>
53
+ requireRecord(runtime[pluginName], `Plugin "${pluginName}" is not configured in the current runtime.`),
54
+ ),
55
+ Effect.flatMap((plugin) =>
56
+ requireRecord(plugin.services, `Plugin "${pluginName}" does not expose a services registry.`),
57
+ ),
58
+ Effect.flatMap((services) =>
59
+ requireRecord(services[serviceName], `Plugin "${pluginName}" service "${serviceName}" is not configured.`).pipe(
60
+ Effect.flatMap((service) =>
61
+ readFunctionEffect(
62
+ service[methodName],
63
+ `Plugin "${pluginName}" service "${serviceName}" is missing method "${methodName}".`,
64
+ ).pipe(
65
+ Effect.map(
66
+ (method) =>
67
+ (...args: unknown[]) =>
68
+ Reflect.apply(method, service, args),
69
+ ),
70
+ ),
71
+ ),
72
+ ),
95
73
  ),
96
74
  )
97
75
  }
98
76
 
99
77
  function tryResolvePluginServiceEffect(
78
+ pluginRuntime: Record<string, unknown> | undefined,
100
79
  pluginName: string,
101
80
  serviceName: string,
102
81
  methodName: string,
103
- ): Effect.Effect<((...args: unknown[]) => PromiseLike<unknown>) | undefined> {
104
- return Effect.catch(resolvePluginServiceEffect(pluginName, serviceName, methodName), () => Effect.succeed(undefined))
82
+ ): Effect.Effect<((...args: unknown[]) => PromiseLike<unknown>) | void, never, never> {
83
+ return Effect.catch(resolvePluginServiceEffect(pluginRuntime, pluginName, serviceName, methodName), () => Effect.void)
105
84
  }
106
85
 
107
- export function getLinearInstallationByOrgId(organizationId: RecordIdRef): Promise<unknown> {
86
+ export function getLinearInstallationByOrgId(
87
+ pluginRuntime: Record<string, unknown> | undefined,
88
+ organizationId: RecordIdRef,
89
+ ): Promise<unknown> {
108
90
  return Effect.runPromise(
109
91
  Effect.gen(function* () {
110
- const fn = yield* tryResolvePluginServiceEffect('linear', 'linearService', 'getInstallationByOrgId')
92
+ const fn = yield* tryResolvePluginServiceEffect(
93
+ pluginRuntime,
94
+ 'linear',
95
+ 'linearService',
96
+ 'getInstallationByOrgId',
97
+ )
111
98
  if (!fn) return null
112
99
  return yield* Effect.tryPromise({
113
100
  try: () => fn(organizationId),
@@ -117,10 +104,18 @@ export function getLinearInstallationByOrgId(organizationId: RecordIdRef): Promi
117
104
  )
118
105
  }
119
106
 
120
- export function getGithubInstallationForOrganization(organizationId: string): Promise<unknown> {
107
+ export function getGithubInstallationForOrganization(
108
+ pluginRuntime: Record<string, unknown> | undefined,
109
+ organizationId: string,
110
+ ): Promise<unknown> {
121
111
  return Effect.runPromise(
122
112
  Effect.gen(function* () {
123
- const fn = yield* tryResolvePluginServiceEffect('github', 'githubService', 'getInstallationForOrganization')
113
+ const fn = yield* tryResolvePluginServiceEffect(
114
+ pluginRuntime,
115
+ 'github',
116
+ 'githubService',
117
+ 'getInstallationForOrganization',
118
+ )
124
119
  if (!fn) return null
125
120
  return yield* Effect.tryPromise({
126
121
  try: () => fn(organizationId),
@@ -137,9 +132,10 @@ const EMPTY_INDEXED_REPO_CONTEXT: LotaRuntimeIndexedRepositoriesContext = {
137
132
  }
138
133
 
139
134
  export function buildIndexedRepositoriesContext(
135
+ adapters: LotaRuntimeAdapters,
140
136
  organizationId: string,
141
137
  ): Promise<LotaRuntimeIndexedRepositoriesContext> {
142
- const buildContext = getRuntimeAdapters().buildIndexedRepositoriesContext
138
+ const buildContext = adapters.buildIndexedRepositoriesContext
143
139
  if (!buildContext) return Promise.resolve(EMPTY_INDEXED_REPO_CONTEXT)
144
140
  return Effect.runPromise(
145
141
  Effect.tryPromise({