@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
@@ -14,6 +14,7 @@ import type { RegularChatMemoryDigestJob } from '../queues/regular-chat-memory-d
14
14
  import { createHelperModelRuntime } from '../runtime/helper-model'
15
15
  import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
16
16
  import { memoryService } from '../services/memory.service'
17
+ import { socialChatHistoryService } from '../services/social-chat-history.service'
17
18
  import { createRegularChatMemoryDigestAgent } from '../system-agents/regular-chat-memory-digest.agent'
18
19
  import { compactWhitespace } from '../utils/string'
19
20
  import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
@@ -48,6 +49,7 @@ const helperModelRuntime = createHelperModelRuntime()
48
49
  interface RegularChatDigestRunResult {
49
50
  skipped: boolean
50
51
  processedWorkstreamMessages: number
52
+ processedSocialMessages: number
51
53
  followUpScheduled: boolean
52
54
  }
53
55
 
@@ -152,7 +154,7 @@ export async function runRegularChatMemoryDigest(
152
154
  const workspaceProvider = getRuntimeAdapters().services?.workspaceProvider
153
155
  if (!workspaceProvider) {
154
156
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider is not configured`
155
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
157
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
156
158
  }
157
159
 
158
160
  return withConfiguredWorkspaceMemoryLock(orgId, async () => {
@@ -162,14 +164,14 @@ export async function runRegularChatMemoryDigest(
162
164
  !workspaceProvider.applyProfileProjection
163
165
  ) {
164
166
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider background/profile methods are incomplete`
165
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
167
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
166
168
  }
167
169
 
168
170
  const workspace = await workspaceProvider.getWorkspace(orgRef)
169
171
  const lifecycleState = await workspaceProvider.getLifecycleState?.(workspace)
170
172
  if (lifecycleState?.bootstrapActive ?? false) {
171
173
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: onboarding is not completed`
172
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
174
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
173
175
  }
174
176
  const projectionState = await workspaceProvider.readProfileProjectionState?.(workspace)
175
177
 
@@ -185,13 +187,23 @@ export async function runRegularChatMemoryDigest(
185
187
  cursor: existingWorkstreamCursor,
186
188
  onboardingCutoff: workstreamOnboardingCutoff,
187
189
  })
190
+ const existingSocialCursor = await socialChatHistoryService.getBackgroundCursor('regular-chat-digest', orgId)
191
+ const socialOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
192
+ hasExistingCursor: existingSocialCursor !== null,
193
+ bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
194
+ })
195
+ const socialMessages = await socialChatHistoryService.listWorkspaceMessages({
196
+ workspaceId: orgId,
197
+ cursor: existingSocialCursor,
198
+ onboardingCutoff: socialOnboardingCutoff,
199
+ })
188
200
 
189
- if (workstreamMessages.length === 0) {
201
+ if (workstreamMessages.length === 0 && socialMessages.length === 0) {
190
202
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: no eligible messages`
191
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
203
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
192
204
  }
193
205
 
194
- const combinedMessages = [...workstreamMessages].sort(compareDigestMessageOrder)
206
+ const combinedMessages = [...workstreamMessages, ...socialMessages].sort(compareDigestMessageOrder)
195
207
  const { transcript, involvedAgentNames } = buildDigestTranscript({ messages: combinedMessages })
196
208
  const existingMemories = await loadExistingOrganizationMemories(orgId)
197
209
 
@@ -219,7 +231,8 @@ export async function runRegularChatMemoryDigest(
219
231
  throw new Error('Regular chat memory digest returned an empty summaryBlock')
220
232
  }
221
233
 
222
- const processedWorkstreamCursor = getLastCursor(combinedMessages)
234
+ const processedWorkstreamCursor = getLastCursor(workstreamMessages)
235
+ const processedSocialCursor = getLastCursor(socialMessages)
223
236
  const digestRunAt = new Date().toISOString()
224
237
 
225
238
  if (synthesis.facts.length > 0) {
@@ -235,6 +248,12 @@ export async function runRegularChatMemoryDigest(
235
248
  digestWorkstreamCursorId: processedWorkstreamCursor.id,
236
249
  }
237
250
  : {}),
251
+ ...(processedSocialCursor
252
+ ? {
253
+ digestSocialCursorCreatedAt: processedSocialCursor.createdAt.toISOString(),
254
+ digestSocialCursorId: processedSocialCursor.id,
255
+ }
256
+ : {}),
238
257
  },
239
258
  agentNames: involvedAgentNames,
240
259
  acquireLock: false,
@@ -248,6 +267,9 @@ export async function runRegularChatMemoryDigest(
248
267
  if (processedWorkstreamCursor) {
249
268
  await workspaceProvider.setBackgroundCursor('regular-chat-digest', orgRef, processedWorkstreamCursor)
250
269
  }
270
+ if (processedSocialCursor) {
271
+ await socialChatHistoryService.setBackgroundCursor('regular-chat-digest', orgId, processedSocialCursor)
272
+ }
251
273
 
252
274
  const workstreamBoundaryCursor = processedWorkstreamCursor ?? existingWorkstreamCursor
253
275
  const hasMoreWorkstreamMessages = await hasNewEligibleWorkstreamMessages({
@@ -255,15 +277,26 @@ export async function runRegularChatMemoryDigest(
255
277
  cursor: workstreamBoundaryCursor,
256
278
  onboardingCutoff: workstreamBoundaryCursor ? null : workstreamOnboardingCutoff,
257
279
  })
280
+ const socialBoundaryCursor = processedSocialCursor ?? existingSocialCursor
281
+ const hasMoreSocialMessages = await socialChatHistoryService.hasWorkspaceMessages({
282
+ workspaceId: orgId,
283
+ cursor: socialBoundaryCursor,
284
+ onboardingCutoff: socialBoundaryCursor ? null : socialOnboardingCutoff,
285
+ })
258
286
 
259
- const followUpScheduled = hasMoreWorkstreamMessages
287
+ const followUpScheduled = hasMoreWorkstreamMessages || hasMoreSocialMessages
260
288
  if (followUpScheduled) {
261
289
  await clearRegularChatMemoryDigestDeduplicationKey(orgId)
262
290
  await enqueueRegularChatMemoryDigest({ orgId })
263
291
  }
264
292
 
265
- serverLogger.info`Regular chat memory digest completed for ${orgId}: workstreamMessages=${workstreamMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
293
+ serverLogger.info`Regular chat memory digest completed for ${orgId}: workstreamMessages=${workstreamMessages.length}, socialMessages=${socialMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
266
294
 
267
- return { skipped: false, processedWorkstreamMessages: workstreamMessages.length, followUpScheduled }
295
+ return {
296
+ skipped: false,
297
+ processedWorkstreamMessages: workstreamMessages.length,
298
+ processedSocialMessages: socialMessages.length,
299
+ followUpScheduled,
300
+ }
268
301
  })
269
302
  }
@@ -6,6 +6,7 @@ import type { SkillExtractionJob } from '../queues/skill-extraction.queue'
6
6
  import { createHelperModelRuntime } from '../runtime/helper-model'
7
7
  import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
8
8
  import { learnedSkillService } from '../services/learned-skill.service'
9
+ import { socialChatHistoryService } from '../services/social-chat-history.service'
9
10
  import { createSkillExtractorAgent, SkillExtractionOutputSchema } from '../system-agents/skill-extractor.agent'
10
11
  import type { SkillCandidate } from '../system-agents/skill-extractor.agent'
11
12
  import { createSkillManagerAgent, SkillManagerOutputSchema } from '../system-agents/skill-manager.agent'
@@ -100,9 +101,24 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
100
101
  hasExistingCursor: existingCursor !== null,
101
102
  bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
102
103
  })
104
+ const existingSocialCursor = await socialChatHistoryService.getBackgroundCursor('skill-extraction', orgId)
105
+ const socialOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
106
+ hasExistingCursor: existingSocialCursor !== null,
107
+ bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
108
+ })
103
109
 
104
110
  const workstreamIds = await listWorkstreamIdsForOrg(orgRef)
105
- const messages = await listEligibleWorkstreamMessages({ workstreamIds, cursor: existingCursor, onboardingCutoff })
111
+ const workstreamMessages = await listEligibleWorkstreamMessages({
112
+ workstreamIds,
113
+ cursor: existingCursor,
114
+ onboardingCutoff,
115
+ })
116
+ const socialMessages = await socialChatHistoryService.listWorkspaceMessages({
117
+ workspaceId: orgId,
118
+ cursor: existingSocialCursor,
119
+ onboardingCutoff: socialOnboardingCutoff,
120
+ })
121
+ const messages = [...workstreamMessages, ...socialMessages]
106
122
 
107
123
  if (messages.length < MIN_MESSAGE_THRESHOLD) {
108
124
  serverLogger.info`Skipping skill extraction for ${orgId}: only ${messages.length} messages (threshold: ${MIN_MESSAGE_THRESHOLD})`
@@ -225,12 +241,16 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
225
241
  }
226
242
  }
227
243
 
228
- const lastMessage = sortedMessages.at(-1)
229
- if (lastMessage) {
230
- await cursorAwareWorkspaceProvider.setBackgroundCursor('skill-extraction', orgRef, lastMessage.cursor)
244
+ const lastWorkstreamMessage = workstreamMessages.at(-1)
245
+ const lastSocialMessage = socialMessages.at(-1)
246
+ if (lastWorkstreamMessage) {
247
+ await cursorAwareWorkspaceProvider.setBackgroundCursor('skill-extraction', orgRef, lastWorkstreamMessage.cursor)
248
+ }
249
+ if (lastSocialMessage) {
250
+ await socialChatHistoryService.setBackgroundCursor('skill-extraction', orgId, lastSocialMessage.cursor)
231
251
  }
232
252
 
233
- serverLogger.info`Skill extraction completed for ${orgId}: messages=${messages.length}, extracted=${extractedSkills}`
253
+ serverLogger.info`Skill extraction completed for ${orgId}: messages=${messages.length}, socialMessages=${socialMessages.length}, extracted=${extractedSkills}`
234
254
 
235
255
  return { skipped: false, processedMessages: messages.length, extractedSkills }
236
256
  })
@@ -1,4 +1,4 @@
1
- import { readdir, readFile } from 'node:fs/promises'
1
+ import { readdir } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
4
  import { RepositoryStructureArtifactSchema, RepositoryStructureSummarySchema } from '@lota-sdk/shared'
@@ -119,7 +119,7 @@ async function collectRelativeFilePaths(rootDir: string, currentDir = ''): Promi
119
119
 
120
120
  async function readPackageJson(rootDir: string, relativePath: string): Promise<PackageJson | null> {
121
121
  try {
122
- const raw = await readFile(path.join(rootDir, relativePath), 'utf8')
122
+ const raw = await Bun.file(path.join(rootDir, relativePath)).text()
123
123
  return JSON.parse(raw) as PackageJson
124
124
  } catch {
125
125
  return null
@@ -9,14 +9,12 @@ import { TABLES } from '../../db/tables'
9
9
  import { WorkstreamMessageRowSchema } from '../../db/workstream-message-row'
10
10
  import type { WorkstreamMessageRow } from '../../db/workstream-message-row'
11
11
  import { normalizeTextBody } from '../../document/parsing'
12
+ import type { LotaRuntimeBackgroundCursor } from '../../runtime/runtime-extensions'
12
13
 
13
- export interface DigestCursor {
14
- createdAt: Date
15
- id: string
16
- }
14
+ export type DigestCursor = LotaRuntimeBackgroundCursor
17
15
 
18
16
  export interface DigestMessage {
19
- source: 'workstream'
17
+ source: 'workstream' | 'social'
20
18
  sourceId: string
21
19
  role: 'system' | 'user' | 'assistant'
22
20
  parts: Array<Record<string, unknown>>
@@ -1,267 +0,0 @@
1
- import { extractAgentMentions, getLeadAgentId } from '../config/agent-defaults'
2
-
3
- export type MessageRoute =
4
- | { type: 'direct'; agents: [string] }
5
- | { type: 'mentions'; agents: string[] }
6
- | { type: 'group-default'; agents: [string] }
7
-
8
- export type ReasoningProfileName = 'fast' | 'standard' | 'deep'
9
-
10
- export interface ReasoningProfile {
11
- name: ReasoningProfileName
12
- maxSteps: number
13
- toolCallBudget: number
14
- maxInputTokensHint: number
15
- }
16
-
17
- export type HighImpactResponseClass =
18
- | 'architecture-recommendation'
19
- | 'gtm-strategy'
20
- | 'pricing-positioning'
21
- | 'product-capability-claim'
22
- | 'community-targeting'
23
- | 'mutating-operation'
24
-
25
- export type PolicyClass = 'external-mutation' | 'security-privacy' | 'legal-compliance' | 'financial-decision'
26
-
27
- export function uniqueMentionOrder(message: string): string[] {
28
- const ordered: string[] = []
29
- const seen = new Set<string>()
30
- for (const mention of extractAgentMentions(message)) {
31
- const agent = mention.agent
32
- if (seen.has(agent)) continue
33
- seen.add(agent)
34
- ordered.push(agent)
35
- }
36
- return ordered
37
- }
38
-
39
- const HIGH_IMPACT_CLASS_PATTERNS: Array<{ className: HighImpactResponseClass; patterns: RegExp[] }> = [
40
- {
41
- className: 'architecture-recommendation',
42
- patterns: [
43
- /\barchitecture\b/i,
44
- /\btech(?:nical)?\s+stack\b/i,
45
- /\binfra(?:structure)?\b/i,
46
- /\brefactor\b/i,
47
- /\bsystem\s+design\b/i,
48
- ],
49
- },
50
- {
51
- className: 'gtm-strategy',
52
- patterns: [
53
- /\bgo[-\s]?to[-\s]?market\b/i,
54
- /\bgtm\b/i,
55
- /\bdistribution\s+strategy\b/i,
56
- /\blaunch\s+strategy\b/i,
57
- /\bcontent\s+strategy\b/i,
58
- /\bdemand\s+gen(?:eration)?\b/i,
59
- ],
60
- },
61
- {
62
- className: 'pricing-positioning',
63
- patterns: [/\bpricing\b/i, /\bpositioning\b/i, /\bpackaging\b/i, /\bfreemium\b/i, /\bbilling\b/i],
64
- },
65
- {
66
- className: 'product-capability-claim',
67
- patterns: [
68
- /\bdoes\s+it\s+(already\s+)?(support|have|do)\b/i,
69
- /\bwhat\s+can\s+(it|we)\s+do\b/i,
70
- /\bis\s+this\s+implemented\b/i,
71
- /\bcurrent(?:ly)?\s+supports?\b/i,
72
- /\bcapabilities?\b/i,
73
- ],
74
- },
75
- {
76
- className: 'community-targeting',
77
- patterns: [
78
- /\bwhere\s+to\s+post\b/i,
79
- /\bcommunity\b/i,
80
- /\btarget\s+audience\b/i,
81
- /\bchannels?\b/i,
82
- /\bdistribution\s+channels?\b/i,
83
- ],
84
- },
85
- {
86
- className: 'mutating-operation',
87
- patterns: [
88
- /\bdelete\s+(issue|comment|task|record|workspace|project)\b/i,
89
- /\bremove\s+(issue|comment|task|record|workspace|project)\b/i,
90
- /\bupdate\s+(issue|comment|task|record|workspace|project)\b/i,
91
- /\bcreate\s+(issue|comment|task|record|workspace|project)\b/i,
92
- /\bchange\s+(issue|comment|task|record|workspace|project)\b/i,
93
- /\barchive\b/i,
94
- /\bskip\s+onboarding\b/i,
95
- /\bproceed\s+in\s+onboarding\b/i,
96
- ],
97
- },
98
- ]
99
-
100
- const POLICY_CLASS_PATTERNS: Array<{ className: PolicyClass; patterns: RegExp[] }> = [
101
- {
102
- className: 'external-mutation',
103
- patterns: [
104
- /\bdelete\s+(issue|comment|task|record|workspace|project)\b/i,
105
- /\bremove\s+(issue|comment|task|record|workspace|project)\b/i,
106
- /\bupdate\s+(issue|comment|task|record|workspace|project)\b/i,
107
- /\bcreate\s+(issue|comment|task|record|workspace|project)\b/i,
108
- /\bchange\s+(issue|comment|task|record|workspace|project)\b/i,
109
- /\barchive\b/i,
110
- /\bskip\s+onboarding\b/i,
111
- /\bproceed\s+in\s+onboarding\b/i,
112
- ],
113
- },
114
- {
115
- className: 'security-privacy',
116
- patterns: [
117
- /\bsecurity\b/i,
118
- /\bprivacy\b/i,
119
- /\bpii\b/i,
120
- /\bauth(?:entication|orization)?\b/i,
121
- /\bencrypt(?:ion|ed)?\b/i,
122
- /\bpermission(?:s)?\b/i,
123
- /\baccess\s+control\b/i,
124
- /\bsecret(?:s)?\b/i,
125
- ],
126
- },
127
- {
128
- className: 'legal-compliance',
129
- patterns: [
130
- /\blegal\b/i,
131
- /\bcompliance\b/i,
132
- /\bregulation(?:s)?\b/i,
133
- /\bgdpr\b/i,
134
- /\bhipaa\b/i,
135
- /\bsoc\s*2\b/i,
136
- /\blicense\b/i,
137
- /\bterms?\b/i,
138
- ],
139
- },
140
- {
141
- className: 'financial-decision',
142
- patterns: [
143
- /\bpricing\b/i,
144
- /\bbilling\b/i,
145
- /\brevenue\b/i,
146
- /\bbudget\b/i,
147
- /\bburn\s+rate\b/i,
148
- /\brunway\b/i,
149
- /\bfundrais(?:e|ing)\b/i,
150
- /\bcash\s+flow\b/i,
151
- ],
152
- },
153
- ]
154
-
155
- const REASONING_PROFILES: Record<ReasoningProfileName, ReasoningProfile> = {
156
- fast: { name: 'fast', maxSteps: 5, toolCallBudget: 3, maxInputTokensHint: 12_000 },
157
- standard: { name: 'standard', maxSteps: 10, toolCallBudget: 6, maxInputTokensHint: 24_000 },
158
- deep: { name: 'deep', maxSteps: 15, toolCallBudget: 10, maxInputTokensHint: 36_000 },
159
- }
160
-
161
- const STRATEGIC_COMPLEXITY_PATTERNS: RegExp[] = [
162
- /\bstrategy\b/i,
163
- /\btrade[-\s]?off\b/i,
164
- /\bcompare\b/i,
165
- /\broadmap\b/i,
166
- /\barchitecture\b/i,
167
- /\bconstraints?\b/i,
168
- /\bprioriti(?:y|es)\b/i,
169
- ]
170
-
171
- const CROSS_SOURCE_COMPLEXITY_PATTERNS: RegExp[] = [
172
- /\bcitation\b/i,
173
- /\bevidence\b/i,
174
- /\bsource\b/i,
175
- /\bground(?:ed|ing)\b/i,
176
- /\brepo\b/i,
177
- /\bmemory\b/i,
178
- ]
179
-
180
- const STANDARD_FLOOR_INTENT_PATTERNS: RegExp[] = [
181
- /\bplan(?:ning)?\b/i,
182
- /\bthink(?:ing)?\b/i,
183
- /\bcareful(?:ly)?\b/i,
184
- /\bimportant\b/i,
185
- /\bcritical(?:ly)?\b/i,
186
- /\bthorough(?:ly)?\b/i,
187
- ]
188
-
189
- export function classifyHighImpactResponse(params: { message: string }): { classes: HighImpactResponseClass[] } {
190
- const message = params.message.trim()
191
- if (!message) return { classes: [] }
192
-
193
- const classes: HighImpactResponseClass[] = []
194
- for (const entry of HIGH_IMPACT_CLASS_PATTERNS) {
195
- if (entry.patterns.some((pattern) => pattern.test(message))) {
196
- classes.push(entry.className)
197
- }
198
- }
199
-
200
- return { classes }
201
- }
202
-
203
- export function classifyPolicyClasses(params: { message: string }): { classes: PolicyClass[] } {
204
- const message = params.message.trim()
205
- if (!message) return { classes: [] }
206
-
207
- const classes: PolicyClass[] = []
208
- for (const entry of POLICY_CLASS_PATTERNS) {
209
- if (entry.patterns.some((pattern) => pattern.test(message))) {
210
- classes.push(entry.className)
211
- }
212
- }
213
-
214
- return { classes }
215
- }
216
-
217
- export function resolveReasoningProfile(params: {
218
- message: string
219
- forceDeep?: boolean
220
- explicitProfile?: ReasoningProfileName
221
- }): ReasoningProfile {
222
- if (params.explicitProfile) {
223
- return REASONING_PROFILES[params.explicitProfile]
224
- }
225
-
226
- const text = params.message.trim()
227
- if (!text) {
228
- return REASONING_PROFILES.standard
229
- }
230
-
231
- let score = 0
232
- if (text.length >= 700) score += 3
233
- else if (text.length >= 350) score += 2
234
- else if (text.length >= 180) score += 1
235
-
236
- const numberedListCount = (text.match(/\n\s*\d+\./g) ?? []).length
237
- if (numberedListCount >= 3) score += 2
238
- else if (numberedListCount >= 1) score += 1
239
-
240
- if (STRATEGIC_COMPLEXITY_PATTERNS.some((pattern) => pattern.test(text))) score += 2
241
- if (CROSS_SOURCE_COMPLEXITY_PATTERNS.some((pattern) => pattern.test(text))) score += 2
242
- if (params.forceDeep) score += 2
243
- if (STANDARD_FLOOR_INTENT_PATTERNS.some((pattern) => pattern.test(text))) {
244
- score += 3
245
- }
246
-
247
- if (score >= 7) return REASONING_PROFILES.deep
248
- if (score >= 3) return REASONING_PROFILES.standard
249
- return REASONING_PROFILES.fast
250
- }
251
-
252
- export function resolveMessageRoute(params: {
253
- workstreamMode: 'direct' | 'group'
254
- workstreamAgentId?: string
255
- message: string
256
- }): MessageRoute {
257
- if (params.workstreamMode === 'direct') {
258
- return { type: 'direct', agents: [params.workstreamAgentId ?? getLeadAgentId()] }
259
- }
260
-
261
- const mentions = uniqueMentionOrder(params.message)
262
- if (mentions.length > 0) {
263
- return { type: 'mentions', agents: mentions }
264
- }
265
-
266
- return { type: 'group-default', agents: [getLeadAgentId()] }
267
- }