@lota-sdk/core 0.1.16 → 0.1.17

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 (46) hide show
  1. package/package.json +6 -3
  2. package/src/ai/definitions.ts +1 -1
  3. package/src/ai/embedding-cache.ts +2 -4
  4. package/src/bifrost/cache-headers.ts +8 -0
  5. package/src/bifrost/index.ts +1 -0
  6. package/src/create-runtime.ts +26 -1
  7. package/src/db/memory-store.helpers.ts +1 -3
  8. package/src/db/schema-fingerprint.ts +1 -3
  9. package/src/queues/document-processor.queue.ts +2 -4
  10. package/src/queues/post-chat-memory.queue.ts +8 -2
  11. package/src/queues/recent-activity-title-refinement.queue.ts +1 -1
  12. package/src/queues/skill-extraction.queue.ts +1 -1
  13. package/src/queues/workstream-title-generation.queue.ts +1 -1
  14. package/src/redis/redis-lease-lock.ts +1 -2
  15. package/src/runtime/agent-runtime-policy.ts +3 -14
  16. package/src/runtime/context-compaction.ts +2 -4
  17. package/src/runtime/index.ts +1 -1
  18. package/src/runtime/runtime-config.ts +86 -2
  19. package/src/runtime/runtime-extensions.ts +0 -1
  20. package/src/runtime/social-chat.ts +752 -0
  21. package/src/runtime/team-consultation-orchestrator.ts +0 -4
  22. package/src/services/agent-executor.service.ts +0 -1
  23. package/src/services/document-chunk.service.ts +1 -3
  24. package/src/services/index.ts +1 -0
  25. package/src/services/memory.service.ts +7 -2
  26. package/src/services/recent-activity.service.ts +1 -3
  27. package/src/services/social-chat-history.service.ts +197 -0
  28. package/src/services/workstream-message.service.ts +1 -3
  29. package/src/services/workstream-turn-preparation.service.ts +0 -23
  30. package/src/system-agents/context-compaction.agent.ts +2 -0
  31. package/src/system-agents/delegated-agent-factory.ts +3 -0
  32. package/src/system-agents/memory-reranker.agent.ts +4 -2
  33. package/src/system-agents/memory.agent.ts +2 -0
  34. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
  35. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
  36. package/src/system-agents/skill-extractor.agent.ts +2 -0
  37. package/src/system-agents/skill-manager.agent.ts +2 -0
  38. package/src/system-agents/title-generator.agent.ts +2 -0
  39. package/src/tools/research-topic.tool.ts +2 -0
  40. package/src/tools/team-think.tool.ts +0 -3
  41. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  42. package/src/workers/regular-chat-memory-digest.runner.ts +43 -10
  43. package/src/workers/skill-extraction.runner.ts +25 -5
  44. package/src/workers/utils/repo-structure-extractor.ts +2 -2
  45. package/src/workers/utils/workstream-message-query.ts +3 -5
  46. package/src/runtime/workstream-routing-policy.ts +0 -267
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "scripts": {
19
- "lint": "node ../node_modules/oxlint/bin/oxlint --fix -c ../oxlint.config.ts src",
19
+ "lint": "bunx oxlint --fix -c ../oxlint.config.ts src",
20
20
  "format": "bunx oxfmt src",
21
21
  "typecheck": "bunx tsgo --noEmit",
22
22
  "test:unit": "bun test ../tests/unit/core",
@@ -29,12 +29,15 @@
29
29
  "dependencies": {
30
30
  "@ai-sdk/devtools": "^0.0.15",
31
31
  "@ai-sdk/openai": "^3.0.48",
32
+ "@chat-adapter/slack": "^4.23.0",
33
+ "@chat-adapter/state-ioredis": "^4.23.0",
32
34
  "@logtape/logtape": "^2.0.5",
33
- "@lota-sdk/shared": "0.1.16",
35
+ "@lota-sdk/shared": "0.1.17",
34
36
  "@mendable/firecrawl-js": "^4.17.0",
35
37
  "@surrealdb/node": "^3.0.3",
36
38
  "ai": "^6.0.137",
37
39
  "bullmq": "^5.71.0",
40
+ "chat": "^4.23.0",
38
41
  "cron-parser": "^5.5.0",
39
42
  "hono": "^4.12.9",
40
43
  "ioredis": "5.9.3",
@@ -283,7 +283,7 @@ export const generalRule = defineRule({
283
283
  name: 'general',
284
284
  instructions: `# General Rules
285
285
 
286
- - Be concise and direct.
286
+ - Be concise and direct. Prefer short, direct responses. Do not pad with context the user did not ask for.
287
287
  - Follow the user's request and constraints.
288
288
  - Ask clarifying questions when necessary.
289
289
  - Be formal and professional when tone is unclear.
@@ -1,10 +1,8 @@
1
- import { createHash } from 'node:crypto'
2
-
3
1
  import type IORedis from 'ioredis'
4
2
 
5
3
  import { aiLogger } from '../config/logger'
6
4
 
7
- export const DEFAULT_EMBEDDING_CACHE_TTL_SECONDS = 3600
5
+ export const DEFAULT_EMBEDDING_CACHE_TTL_SECONDS = 7200
8
6
  const EMBEDDING_CACHE_KEY_PREFIX = 'emb'
9
7
 
10
8
  export class EmbeddingCache {
@@ -14,7 +12,7 @@ export class EmbeddingCache {
14
12
  ) {}
15
13
 
16
14
  private buildKey(model: string, text: string): string {
17
- const hash = createHash('sha256').update(text).digest('hex')
15
+ const hash = new Bun.CryptoHasher('sha256').update(text).digest('hex')
18
16
  return `${EMBEDDING_CACHE_KEY_PREFIX}:${model}:${hash}`
19
17
  }
20
18
 
@@ -0,0 +1,8 @@
1
+ const BIFROST_CACHE_KEY_HEADER = 'x-bf-cache-key'
2
+ const BIFROST_CACHE_TTL_HEADER = 'x-bf-cache-ttl'
3
+
4
+ export function buildBifrostCacheHeaders(cacheKey: string, ttl?: string): Record<string, string> {
5
+ const headers: Record<string, string> = { [BIFROST_CACHE_KEY_HEADER]: cacheKey }
6
+ if (ttl) headers[BIFROST_CACHE_TTL_HEADER] = ttl
7
+ return headers
8
+ }
@@ -1 +1,2 @@
1
1
  export * from './bifrost'
2
+ export * from './cache-headers'
@@ -25,6 +25,8 @@ import type { LotaRuntimeConfig, ResolvedLotaRuntimeConfig } from './runtime/run
25
25
  import { configureRuntimeExtensions } from './runtime/runtime-extensions'
26
26
  import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
27
27
  import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
28
+ import type { LotaRuntimeSocialChat } from './runtime/social-chat'
29
+ import { createSocialChatRuntime } from './runtime/social-chat'
28
30
  import type { attachmentService } from './services/attachment.service'
29
31
  import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
30
32
  import { coordinationRegistryService as coordinationRegistryServiceSingleton } from './services/coordination-registry.service'
@@ -47,6 +49,10 @@ import type { recentActivityTitleService } from './services/recent-activity-titl
47
49
  import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
48
50
  import type { recentActivityService } from './services/recent-activity.service'
49
51
  import { recentActivityService as recentActivityServiceSingleton } from './services/recent-activity.service'
52
+ import {
53
+ configureSocialChatHistory,
54
+ socialChatHistoryService as socialChatHistoryServiceSingleton,
55
+ } from './services/social-chat-history.service'
50
56
  import { getBuiltInSystemExecutors } from './services/system-executor.service'
51
57
  import type { userService } from './services/user.service'
52
58
  import { userService as userServiceSingleton } from './services/user.service'
@@ -115,6 +121,7 @@ export interface LotaRuntime {
115
121
  userService: typeof userService
116
122
  recentActivityService: typeof recentActivityService
117
123
  recentActivityTitleService: typeof recentActivityTitleService
124
+ socialChatHistoryService: typeof socialChatHistoryServiceSingleton
118
125
  executionPlanService: typeof executionPlanService
119
126
  workstreamMessageService: typeof workstreamMessageService
120
127
  workstreamService: typeof workstreamService
@@ -184,6 +191,7 @@ export interface LotaRuntime {
184
191
  closeConnection: () => Promise<void>
185
192
  }
186
193
  workers: LotaRuntimeWorkers
194
+ socialChat: LotaRuntimeSocialChat
187
195
  schemaFiles: Array<string | URL>
188
196
  contributions: { envKeys: readonly string[]; schemaFiles: Array<string | URL> }
189
197
  config: ResolvedLotaRuntimeConfig
@@ -218,11 +226,21 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
218
226
  setRedisConnectionManager(redisManager)
219
227
  configureEmbeddingCache(redisManager.getConnection(), runtimeConfig.memory.embeddingCacheTtlSeconds)
220
228
  configureBackgroundProcessing(runtimeConfig.backgroundProcessing)
229
+ configureSocialChatHistory({ keyPrefix: runtimeConfig.socialChat?.historyRedisKeyPrefix })
230
+
231
+ const socialChatAgentId = runtimeConfig.socialChat?.agentId?.trim() || 'socialChat'
232
+ const socialChatAgentDisplayName = runtimeConfig.socialChat?.agentDisplayName?.trim() || 'Lota'
233
+ if (runtimeConfig.socialChat && !runtimeConfig.agents.roster.includes(socialChatAgentId)) {
234
+ throw new Error(`socialChat.agentId must be present in agents.roster: ${socialChatAgentId}`)
235
+ }
236
+ const agentDisplayNames = runtimeConfig.socialChat
237
+ ? { ...runtimeConfig.agents.displayNames, [socialChatAgentId]: socialChatAgentDisplayName }
238
+ : runtimeConfig.agents.displayNames
221
239
 
222
240
  configureAgents({
223
241
  roster: runtimeConfig.agents.roster,
224
242
  leadAgentId: runtimeConfig.agents.leadAgentId,
225
- displayNames: runtimeConfig.agents.displayNames,
243
+ displayNames: agentDisplayNames,
226
244
  shortDisplayNames: runtimeConfig.agents.shortDisplayNames,
227
245
  teamConsultParticipants: runtimeConfig.agents.teamConsultParticipants,
228
246
  getCoreWorkstreamProfile: runtimeConfig.agents.getCoreWorkstreamProfile,
@@ -264,6 +282,10 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
264
282
  const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
265
283
  const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
266
284
  const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
285
+ const socialChat = createSocialChatRuntime({
286
+ redisClient: redisManager.getConnection(),
287
+ socialChat: runtimeConfig.socialChat,
288
+ })
267
289
 
268
290
  const lota = {
269
291
  organizations: {
@@ -369,6 +391,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
369
391
  userService: userServiceSingleton,
370
392
  recentActivityService: recentActivityServiceSingleton,
371
393
  recentActivityTitleService: recentActivityTitleServiceSingleton,
394
+ socialChatHistoryService: socialChatHistoryServiceSingleton,
372
395
  executionPlanService: executionPlanServiceSingleton,
373
396
  workstreamMessageService: workstreamMessageServiceSingleton,
374
397
  workstreamService: workstreamServiceSingleton,
@@ -388,6 +411,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
388
411
  closeConnection: async () => await redisManager.closeConnection(),
389
412
  },
390
413
  workers,
414
+ socialChat,
391
415
  schemaFiles,
392
416
  contributions: { envKeys: [...new Set(contributionEnvKeys)], schemaFiles: hostContributionSchemaFiles },
393
417
  config: runtimeConfig,
@@ -412,6 +436,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
412
436
  disconnected = true
413
437
 
414
438
  try {
439
+ await socialChat.shutdown()
415
440
  await closeSharedSubscriber()
416
441
  await db.disconnect()
417
442
  await redisManager.closeConnection()
@@ -1,5 +1,3 @@
1
- import { createHash } from 'node:crypto'
2
-
3
1
  import type { BasicSearchRow, SurrealMemoryRow } from './memory-store.rows'
4
2
  import type { MemoryRecord, MemorySearchResult } from './memory-types'
5
3
  import { recordIdToString } from './record-id'
@@ -114,5 +112,5 @@ export function processGraphAwareRows<T extends BasicSearchRow>(
114
112
  }
115
113
 
116
114
  export function hashContent(content: string, scopeId: string, memoryType: string): string {
117
- return createHash('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
115
+ return new Bun.CryptoHasher('sha256').update(`${scopeId}:${memoryType}:${content}`).digest('hex')
118
116
  }
@@ -1,11 +1,9 @@
1
- import { createHash } from 'node:crypto'
2
-
3
1
  function toSchemaFilePath(value: string | URL): string {
4
2
  return value instanceof URL ? value.pathname : value
5
3
  }
6
4
 
7
5
  export async function computeSchemaFingerprint(schemaFiles: readonly (string | URL)[]): Promise<string> {
8
- const hash = createHash('sha256')
6
+ const hash = new Bun.CryptoHasher('sha256')
9
7
 
10
8
  // Sequential reads required: hash must be computed in deterministic file order
11
9
  for (const schemaFile of schemaFiles) {
@@ -1,5 +1,3 @@
1
- import { createHash } from 'node:crypto'
2
-
3
1
  import { Queue, Worker } from 'bullmq'
4
2
  import type IORedis from 'ioredis'
5
3
 
@@ -43,7 +41,7 @@ export function buildDocumentProcessorJobId(
43
41
  'orgId' | 'source' | 'sourceId' | 'sourceCanonicalKey' | 'sourceVersionKey' | 'title'
44
42
  >,
45
43
  ): string {
46
- const digest = createHash('sha256')
44
+ const digest = new Bun.CryptoHasher('sha256')
47
45
  .update(
48
46
  JSON.stringify({
49
47
  orgId: job.orgId,
@@ -73,7 +71,7 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
73
71
  } {
74
72
  const queueName = params.queueName ?? DEFAULT_DOCUMENT_PROCESSOR_QUEUE
75
73
  const workerName = params.workerName ?? DEFAULT_WORKER_NAME
76
- const concurrency = params.concurrency ?? 4
74
+ const concurrency = params.concurrency ?? 10
77
75
  const lockDuration = params.lockDuration ?? 300_000
78
76
  const jobName = 'process-document' as Parameters<Queue<TJob, unknown, string>['add']>[0]
79
77
  const toQueueData = (job: TJob): Parameters<Queue<TJob, unknown, string>['add']>[1] =>
@@ -15,6 +15,8 @@ interface PostChatMemoryExtractionJob {
15
15
  orgId: string
16
16
  workstreamId: string
17
17
  sourceId: string
18
+ source?: string
19
+ sourceMetadata?: Record<string, unknown>
18
20
  onboardStatus?: OrganizationOnboardStatus
19
21
  userMessage: string
20
22
  historyMessages: PostChatMemoryMessage[]
@@ -54,6 +56,8 @@ async function processPostChatMemoryJob(job: Job<PostChatMemoryExtractionJob>):
54
56
  input: userMessage,
55
57
  output: joinedOutput,
56
58
  sourceId: data.sourceId,
59
+ source: data.source,
60
+ sourceMetadata: data.sourceMetadata,
57
61
  onboardStatus: data.onboardStatus,
58
62
  ...(uniqueAgentNames.length > 0 ? { agentName: uniqueAgentNames[0] } : {}),
59
63
  historyMessages: data.historyMessages,
@@ -67,7 +71,7 @@ const postChatMemory = createQueueFactory<PostChatMemoryExtractionJob>({
67
71
  name: 'post-chat-memory',
68
72
  displayName: 'Post-chat memory',
69
73
  jobName: 'extract-memory',
70
- concurrency: 3,
74
+ concurrency: 10,
71
75
  lockDuration: 900_000,
72
76
  maxStalledCount: 10,
73
77
  stalledInterval: 120_000,
@@ -75,7 +79,9 @@ const postChatMemory = createQueueFactory<PostChatMemoryExtractionJob>({
75
79
  processor: processPostChatMemoryJob,
76
80
  })
77
81
 
78
- export const enqueuePostChatMemory = postChatMemory.enqueue
82
+ export function enqueuePostChatMemory(job: PostChatMemoryExtractionJob, options?: { dedupeKey?: string }) {
83
+ return postChatMemory.enqueue(job, options?.dedupeKey ? { jobId: options.dedupeKey } : undefined)
84
+ }
79
85
  export const startPostChatMemoryWorker = postChatMemory.startWorker
80
86
 
81
87
  if (import.meta.main) {
@@ -17,7 +17,7 @@ const recentActivityTitleRefinement = createQueueFactory<RecentActivityTitleRefi
17
17
  name: 'recent-activity-title-refinement',
18
18
  displayName: 'Recent activity title refinement',
19
19
  jobName: 'refine-recent-activity-title',
20
- concurrency: 2,
20
+ concurrency: 10,
21
21
  lockDuration: 300_000,
22
22
  defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 2_000 } },
23
23
  processor: processRecentActivityTitleRefinementJob,
@@ -10,7 +10,7 @@ const skillExtraction = createQueueFactory<SkillExtractionJob>({
10
10
  name: 'skill-extraction',
11
11
  displayName: 'Skill extraction',
12
12
  jobName: 'run-extraction',
13
- concurrency: 1,
13
+ concurrency: 10,
14
14
  lockDuration: 600_000,
15
15
  defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5000 } },
16
16
  processorPath: getWorkerPath('skill-extraction.worker.ts'),
@@ -20,7 +20,7 @@ const workstreamTitleGeneration = createQueueFactory<WorkstreamTitleGenerationJo
20
20
  name: 'workstream-title-generation',
21
21
  displayName: 'Workstream title generation',
22
22
  jobName: 'generate-workstream-title',
23
- concurrency: 2,
23
+ concurrency: 10,
24
24
  lockDuration: 60_000,
25
25
  defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 2_000 } },
26
26
  processor: processWorkstreamTitleGenerationJob,
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto'
2
1
  import { setTimeout as delay } from 'node:timers/promises'
3
2
 
4
3
  import type IORedis from 'ioredis'
@@ -147,7 +146,7 @@ export async function withRedisLeaseLock<T>(
147
146
  const heldInfoThresholdMs = options.heldInfoThresholdMs ?? 5_000
148
147
  const label = options.label ?? 'redis lease lock'
149
148
 
150
- const lockValue = randomUUID()
149
+ const lockValue = crypto.randomUUID()
151
150
  const waitStart = Date.now()
152
151
  await acquireLeaseLock({ ...options, lockKey, lockValue, label, retryDelayMs, waitLogIntervalMs, maxWaitMs })
153
152
  const waitedMs = Date.now() - waitStart
@@ -3,18 +3,12 @@ import type { ExecutionMode, PlanArtifactSubmission, PlanNodeSpec } from '@lota-
3
3
  import { getLeadAgentId } from '../config/agent-defaults'
4
4
  import { resolveOnboardingOwnerAgentId } from '../config/workstream-defaults'
5
5
  import type { ChatMode } from './agent-types'
6
- import { resolveReasoningProfile } from './workstream-routing-policy'
7
- import type { ReasoningProfileName } from './workstream-routing-policy'
8
-
9
6
  export interface AgentRuntimeConfig<TAgent extends string> {
10
7
  id: TAgent
11
8
  displayName: string
12
9
  mode: ChatMode
13
10
  extraInstructions?: string
14
11
  maxSteps: number
15
- reasoningProfile: ReasoningProfileName
16
- toolCallBudget: number
17
- maxInputTokensHint: number
18
12
  }
19
13
 
20
14
  export interface AgentToolPolicy<TSkill extends PropertyKey> {
@@ -100,7 +94,6 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
100
94
  skills?: TSkill[]
101
95
  onboardingActive: boolean
102
96
  linearInstalled: boolean
103
- reasoningProfile?: ReasoningProfileName
104
97
  systemWorkspaceDetails?: string
105
98
  preSeededMemoriesSection?: string
106
99
  retrievedKnowledgeSection?: string
@@ -115,7 +108,6 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
115
108
  buildOnboardingPromptSection: () => string
116
109
  }): AgentRuntimeConfig<TAgent> {
117
110
  const mode = params.mode ?? toChatMode(params.workstreamMode)
118
- const profile = resolveReasoningProfile({ message: '', explicitProfile: params.reasoningProfile ?? 'standard' })
119
111
  const rulesSection = params.buildGlobalRuleInstructionSection()
120
112
  const skillsSection =
121
113
  params.skills && params.skills.length > 0 ? params.buildSkillInstructionSection(params.skills) : ''
@@ -141,10 +133,7 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
141
133
  displayName: params.displayNameByAgent[params.agentId] ?? params.agentId,
142
134
  mode,
143
135
  extraInstructions,
144
- maxSteps: profile.maxSteps,
145
- reasoningProfile: profile.name,
146
- toolCallBudget: profile.toolCallBudget,
147
- maxInputTokensHint: profile.maxInputTokensHint,
136
+ maxSteps: 15,
148
137
  }
149
138
  }
150
139
 
@@ -180,8 +169,8 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
180
169
  includeReadFileParts: true,
181
170
  includeInspectWebsite: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
182
171
  includeProceedInOnboarding: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
183
- includeGithubIntegration: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
184
- includeIndexRepositoryByURL: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
172
+ includeGithubIntegration: params.agentId === onboardingOwnerAgentId,
173
+ includeIndexRepositoryByURL: params.agentId === onboardingOwnerAgentId,
185
174
  includeIndexedRepository: params.githubInstalled && params.provideRepoTool,
186
175
  }
187
176
  }
@@ -1,5 +1,3 @@
1
- import { createHash, randomUUID } from 'node:crypto'
2
-
3
1
  import type { ChatMessage } from '@lota-sdk/shared'
4
2
 
5
3
  import { CHARS_PER_TOKEN_ESTIMATE, compactWhitespace, readRecord, readString, stringifyUnknown } from '../utils/string'
@@ -108,7 +106,7 @@ function createStableId(prefix: string, ...parts: Array<string | number | undefi
108
106
  .map((part) => (part === undefined ? '' : String(part)))
109
107
  .map((part) => compactWhitespace(part))
110
108
  .join('|')
111
- const hash = createHash('sha1').update(`${prefix}|${payload}`).digest('hex').slice(0, 20)
109
+ const hash = new Bun.CryptoHasher('sha1').update(`${prefix}|${payload}`).digest('hex').slice(0, 20)
112
110
  return `${prefix}_${hash}`
113
111
  }
114
112
 
@@ -532,7 +530,7 @@ export function createContextCompactionRuntime(
532
530
  options: CreateContextCompactionRuntimeOptions,
533
531
  ): ContextCompactionRuntime {
534
532
  const now = options.now ?? (() => Date.now())
535
- const randomId = options.randomId ?? (() => randomUUID())
533
+ const randomId = options.randomId ?? (() => crypto.randomUUID())
536
534
  const thresholdRatio = options.thresholdRatio ?? CONTEXT_COMPACTION_THRESHOLD_RATIO
537
535
  const outputReserveTokens = options.outputReserveTokens ?? CONTEXT_OUTPUT_RESERVE_TOKENS
538
536
  const safetyMarginTokens = options.safetyMarginTokens ?? CONTEXT_SAFETY_MARGIN_TOKENS
@@ -20,11 +20,11 @@ export * from './runtime-config'
20
20
  export * from './runtime-extensions'
21
21
  export * from './runtime-worker-registry'
22
22
  export * from './skill-extraction-policy'
23
+ export * from './social-chat'
23
24
  export * from './team-consultation-orchestrator'
24
25
  export * from './team-consultation-prompts'
25
26
  export * from './turn-lifecycle'
26
27
  export * from './workstream-chat-helpers'
27
- export * from './workstream-routing-policy'
28
28
  export {
29
29
  WorkstreamStateSchema,
30
30
  type WorkstreamState,
@@ -1,8 +1,10 @@
1
+ import type { ToolSet } from 'ai'
1
2
  import { z } from 'zod'
2
3
 
3
4
  import type { CoreWorkstreamProfile } from '../config/agent-defaults'
4
5
  import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
5
6
  import type { LotaWorkstreamConfig, WorkstreamBootstrapWelcomeConfig } from '../config/workstream-defaults'
7
+ import type { RecordIdRef } from '../db/record-id'
6
8
  import type { NotificationService } from '../services/notification.service'
7
9
  import { isRecord } from '../utils/string'
8
10
  import type { GraphDesigner } from './graph-designer'
@@ -61,6 +63,87 @@ function isWorkerExtensionRecord(value: unknown): value is LotaRuntimeWorkerExte
61
63
  return true
62
64
  }
63
65
 
66
+ export interface LotaSocialChatSlackConfig {
67
+ botToken?: string
68
+ signingSecret?: string
69
+ userName?: string
70
+ dedupeTtlMs?: number
71
+ }
72
+
73
+ export interface LotaSocialChatResolveContextParams {
74
+ platform: 'slack'
75
+ channelId: string
76
+ threadId: string
77
+ messageId: string
78
+ text: string
79
+ authorId?: string
80
+ authorName?: string
81
+ }
82
+
83
+ export interface LotaSocialChatResolvedContext {
84
+ workspaceId: RecordIdRef
85
+ userId: RecordIdRef
86
+ userName?: string | null
87
+ }
88
+
89
+ export interface BuildSocialChatAgentToolsParams {
90
+ agentId: string
91
+ workspaceId: RecordIdRef
92
+ workspaceIdString: string
93
+ userId: RecordIdRef
94
+ userIdString: string
95
+ userName?: string | null
96
+ platform: 'slack'
97
+ channelId: string
98
+ threadId: string
99
+ incomingMessageId: string
100
+ incomingText: string
101
+ memoryBlock: string
102
+ onAppendMemoryBlock: (value: string) => void
103
+ context?: Record<string, unknown> | null
104
+ }
105
+
106
+ export interface LotaRuntimeSocialChatConfig {
107
+ agentId?: string
108
+ agentDisplayName?: string
109
+ slack?: LotaSocialChatSlackConfig
110
+ historyRedisKeyPrefix?: string
111
+ stateRedisKeyPrefix?: string
112
+ resolveContext: (
113
+ params: LotaSocialChatResolveContextParams,
114
+ ) => LotaSocialChatResolvedContext | Promise<LotaSocialChatResolvedContext>
115
+ buildAgentTools: (params: BuildSocialChatAgentToolsParams) => ToolSet | Promise<ToolSet>
116
+ getConsultParticipants?:
117
+ | ((params: {
118
+ workspaceId: RecordIdRef
119
+ workspaceIdString: string
120
+ platform: 'slack'
121
+ }) => string[] | Promise<string[]>)
122
+ | undefined
123
+ }
124
+
125
+ function isSlackSocialChatConfig(value: unknown): value is LotaSocialChatSlackConfig {
126
+ if (!isRecord(value)) return false
127
+ if (value.botToken !== undefined && typeof value.botToken !== 'string') return false
128
+ if (value.signingSecret !== undefined && typeof value.signingSecret !== 'string') return false
129
+ if (value.userName !== undefined && typeof value.userName !== 'string') return false
130
+ if (value.dedupeTtlMs !== undefined && typeof value.dedupeTtlMs !== 'number') return false
131
+ return true
132
+ }
133
+
134
+ function isSocialChatConfig(value: unknown): value is LotaRuntimeSocialChatConfig {
135
+ if (!isRecord(value)) return false
136
+ if (value.agentId !== undefined && typeof value.agentId !== 'string') return false
137
+ if (value.agentDisplayName !== undefined && typeof value.agentDisplayName !== 'string') return false
138
+ if (value.historyRedisKeyPrefix !== undefined && typeof value.historyRedisKeyPrefix !== 'string') return false
139
+ if (value.stateRedisKeyPrefix !== undefined && typeof value.stateRedisKeyPrefix !== 'string') return false
140
+ if (value.slack !== undefined && !isSlackSocialChatConfig(value.slack)) return false
141
+ if (!isFunction(value.resolveContext)) return false
142
+ if (!isFunction(value.buildAgentTools)) return false
143
+ if (value.getConsultParticipants !== undefined && !isFunction(value.getConsultParticipants)) return false
144
+ return true
145
+ }
146
+
64
147
  const workstreamBootstrapWelcomeConfigSchema = z.object({
65
148
  directAgentId: z.string().trim().min(1),
66
149
  buildMessageText: z.custom<WorkstreamBootstrapWelcomeConfig['buildMessageText']>(isFunction, {
@@ -161,9 +244,9 @@ export const LotaRuntimeConfigSchema = z.object({
161
244
  memory: z
162
245
  .object({
163
246
  searchK: z.coerce.number().int().positive().default(6),
164
- embeddingCacheTtlSeconds: z.coerce.number().int().positive().default(3600),
247
+ embeddingCacheTtlSeconds: z.coerce.number().int().positive().default(7200),
165
248
  })
166
- .default({ searchK: 6, embeddingCacheTtlSeconds: 3600 }),
249
+ .default({ searchK: 6, embeddingCacheTtlSeconds: 7200 }),
167
250
  workstreams: workstreamConfigSchema.default({}),
168
251
  backgroundProcessing: z
169
252
  .object({
@@ -188,6 +271,7 @@ export const LotaRuntimeConfigSchema = z.object({
188
271
  runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
189
272
  turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
190
273
  graphDesigner: z.custom<GraphDesigner>(isGraphDesigner).optional(),
274
+ socialChat: z.custom<LotaRuntimeSocialChatConfig>(isSocialChatConfig).optional(),
191
275
  })
192
276
 
193
277
  export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
@@ -123,7 +123,6 @@ export interface ResolveAgentParams {
123
123
  onboardingActive: boolean
124
124
  linearInstalled: boolean
125
125
  githubInstalled: boolean
126
- reasoningProfile: string
127
126
  skills?: string[]
128
127
  additionalInstructionSections?: string[]
129
128
  context: Record<string, unknown> | null