@lota-sdk/core 0.1.24 → 0.1.25
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.
- package/package.json +2 -2
- package/src/ai/definitions.ts +5 -59
- package/src/ai-gateway/ai-gateway.ts +36 -28
- package/src/ai-gateway/cache-headers.ts +9 -0
- package/src/config/model-constants.ts +6 -2
- package/src/create-runtime.ts +1 -17
- package/src/db/memory-types.ts +13 -8
- package/src/db/memory.ts +74 -53
- package/src/queues/autonomous-job.queue.ts +1 -8
- package/src/queues/context-compaction.queue.ts +2 -2
- package/src/queues/index.ts +2 -6
- package/src/queues/organization-learning.queue.ts +78 -0
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -16
- package/src/queues/title-generation.queue.ts +62 -0
- package/src/runtime/agent-prompt-context.ts +0 -18
- package/src/runtime/agent-runtime-policy.ts +9 -2
- package/src/runtime/context-compaction-constants.ts +4 -2
- package/src/runtime/context-compaction.ts +135 -118
- package/src/runtime/memory-pipeline.ts +70 -1
- package/src/runtime/memory-prompts-fact.ts +16 -0
- package/src/runtime/plugin-resolution.ts +3 -2
- package/src/runtime/plugin-types.ts +1 -42
- package/src/runtime/post-turn-side-effects.ts +212 -0
- package/src/runtime/runtime-config.ts +0 -13
- package/src/runtime/runtime-extensions.ts +10 -16
- package/src/runtime/runtime-worker-registry.ts +8 -19
- package/src/runtime/social-chat-agent-runner.ts +119 -0
- package/src/runtime/social-chat-history.ts +110 -0
- package/src/runtime/social-chat-prompts.ts +58 -0
- package/src/runtime/social-chat.ts +104 -340
- package/src/runtime/specialist-runner.ts +18 -0
- package/src/runtime/workstream-chat-helpers.ts +19 -0
- package/src/runtime/workstream-plan-turn.ts +195 -0
- package/src/runtime/workstream-state.ts +11 -8
- package/src/runtime/workstream-turn-context.ts +183 -0
- package/src/services/autonomous-job.service.ts +1 -8
- package/src/services/execution-plan.service.ts +205 -334
- package/src/services/index.ts +1 -4
- package/src/services/memory.service.ts +54 -44
- package/src/services/ownership-dispatcher.service.ts +2 -19
- package/src/services/plan-completion-side-effects.ts +80 -0
- package/src/services/plan-event-delivery.service.ts +1 -1
- package/src/services/plan-executor.service.ts +42 -190
- package/src/services/plan-node-spec.ts +60 -0
- package/src/services/plan-run-data.ts +88 -0
- package/src/services/plan-validator.service.ts +10 -8
- package/src/services/workstream-constants.ts +2 -0
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.service.ts +208 -715
- package/src/services/workstream.service.ts +162 -192
- package/src/services/workstream.types.ts +12 -44
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -0
- package/src/tools/execution-plan.tool.ts +7 -6
- package/src/tools/remember-memory.tool.ts +7 -10
- package/src/tools/research-topic.tool.ts +1 -1
- package/src/tools/team-think.tool.ts +1 -1
- package/src/tools/user-questions.tool.ts +1 -1
- package/src/utils/autonomous-job-ids.ts +7 -0
- package/src/workers/organization-learning.worker.ts +31 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +9 -3
- package/src/workers/skill-extraction.runner.ts +2 -2
- package/src/queues/recent-activity-title-refinement.queue.ts +0 -30
- package/src/queues/regular-chat-memory-digest.config.ts +0 -12
- package/src/queues/regular-chat-memory-digest.queue.ts +0 -34
- package/src/queues/skill-extraction.config.ts +0 -9
- package/src/queues/skill-extraction.queue.ts +0 -27
- package/src/queues/workstream-title-generation.queue.ts +0 -33
- package/src/services/context-enrichment.service.ts +0 -33
- package/src/services/coordination-registry.service.ts +0 -117
- package/src/services/domain-agent-executor.service.ts +0 -71
- package/src/services/memory-assessment.service.ts +0 -44
- package/src/services/playbook-registry.service.ts +0 -67
- package/src/workers/regular-chat-memory-digest.worker.ts +0 -22
- package/src/workers/skill-extraction.worker.ts +0 -22
|
@@ -9,6 +9,7 @@ const SCORE_WEIGHTS = {
|
|
|
9
9
|
interface MemoryFactInput {
|
|
10
10
|
content: string
|
|
11
11
|
confidence: number
|
|
12
|
+
importance?: number
|
|
12
13
|
durability?: string
|
|
13
14
|
type?: string
|
|
14
15
|
}
|
|
@@ -23,6 +24,7 @@ interface MemoryImportanceParams {
|
|
|
23
24
|
text: string
|
|
24
25
|
memoryType: string
|
|
25
26
|
explicitImportance?: number
|
|
27
|
+
extractedImportanceByKey: Map<string, number>
|
|
26
28
|
confidenceByKey: Map<string, number>
|
|
27
29
|
durabilityByKey: Map<string, string>
|
|
28
30
|
categoryByKey: Map<string, string | undefined>
|
|
@@ -30,6 +32,7 @@ interface MemoryImportanceParams {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
interface MemoryFactMaps {
|
|
35
|
+
extractedImportanceByKey: Map<string, number>
|
|
33
36
|
confidenceByKey: Map<string, number>
|
|
34
37
|
durabilityByKey: Map<string, string>
|
|
35
38
|
categoryByKey: Map<string, string | undefined>
|
|
@@ -195,18 +198,26 @@ export function postProcessMemoryFacts<T extends MemoryFactInput>(
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
export function buildMemoryFactMaps<T extends MemoryFactInput>(facts: T[]): MemoryFactMaps {
|
|
201
|
+
const extractedImportanceByKey = new Map<string, number>()
|
|
198
202
|
const confidenceByKey = new Map<string, number>()
|
|
199
203
|
const durabilityByKey = new Map<string, string>()
|
|
200
204
|
const categoryByKey = new Map<string, string | undefined>()
|
|
201
205
|
|
|
202
206
|
for (const fact of facts) {
|
|
203
207
|
const key = normalizeMemoryKey(fact.content)
|
|
208
|
+
const extractedImportance =
|
|
209
|
+
typeof (fact as T & { importance?: number }).importance === 'number'
|
|
210
|
+
? clampImportance((fact as T & { importance: number }).importance)
|
|
211
|
+
: undefined
|
|
212
|
+
if (extractedImportance !== undefined) {
|
|
213
|
+
extractedImportanceByKey.set(key, extractedImportance)
|
|
214
|
+
}
|
|
204
215
|
confidenceByKey.set(key, fact.confidence)
|
|
205
216
|
durabilityByKey.set(key, fact.durability ?? 'standard')
|
|
206
217
|
categoryByKey.set(key, fact.type)
|
|
207
218
|
}
|
|
208
219
|
|
|
209
|
-
return { confidenceByKey, durabilityByKey, categoryByKey }
|
|
220
|
+
return { extractedImportanceByKey, confidenceByKey, durabilityByKey, categoryByKey }
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
function toUniqueOrderedIds(value: string[] | undefined, validIds: Set<string>): string[] {
|
|
@@ -402,6 +413,56 @@ export function compileMemoryUpdatesFromDelta(params: {
|
|
|
402
413
|
return { memory: updates }
|
|
403
414
|
}
|
|
404
415
|
|
|
416
|
+
export function projectMemoryDeltaToScope(params: {
|
|
417
|
+
delta: MemoryDeltaOutputLike
|
|
418
|
+
scopeMemoryIds: readonly string[]
|
|
419
|
+
scopeMemoryIdsByUnionId?: Readonly<Record<string, readonly string[]>>
|
|
420
|
+
}): MemoryDeltaOutputLike {
|
|
421
|
+
const validExistingIds = new Set(params.scopeMemoryIds)
|
|
422
|
+
const mapTargetMemoryIds = (targetIds: readonly string[] | undefined): string[] => {
|
|
423
|
+
if (!targetIds || targetIds.length === 0) return []
|
|
424
|
+
|
|
425
|
+
const mappedIds = targetIds.flatMap((targetId) => {
|
|
426
|
+
const directId = targetId.trim()
|
|
427
|
+
if (!directId) return []
|
|
428
|
+
|
|
429
|
+
const aliasedIds = params.scopeMemoryIdsByUnionId?.[directId]
|
|
430
|
+
if (aliasedIds && aliasedIds.length > 0) {
|
|
431
|
+
return aliasedIds.filter((id) => validExistingIds.has(id))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return validExistingIds.has(directId) ? [directId] : []
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
return [...new Set(mappedIds)]
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
deltas: params.delta.deltas.map((delta) => {
|
|
442
|
+
const targetMemoryIds = mapTargetMemoryIds(delta.targetMemoryIds)
|
|
443
|
+
const invalidateTargetIds = mapTargetMemoryIds(delta.invalidateTargetIds).filter((id) =>
|
|
444
|
+
targetMemoryIds.includes(id),
|
|
445
|
+
)
|
|
446
|
+
const relations = (delta.relations ?? []).flatMap((relation) => {
|
|
447
|
+
if (typeof relation.targetFactIndex === 'number') return [relation]
|
|
448
|
+
if (typeof relation.targetMemoryId !== 'string') return []
|
|
449
|
+
|
|
450
|
+
return mapTargetMemoryIds([relation.targetMemoryId]).map((targetMemoryId) => ({ ...relation, targetMemoryId }))
|
|
451
|
+
})
|
|
452
|
+
const hasScopeTarget = targetMemoryIds.length > 0
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
fact: delta.fact,
|
|
456
|
+
classification: hasScopeTarget || delta.classification === 'new' ? delta.classification : 'new',
|
|
457
|
+
targetMemoryIds,
|
|
458
|
+
invalidateTargetIds,
|
|
459
|
+
relations,
|
|
460
|
+
rationale: delta.rationale ?? 'Projected onto the current memory scope.',
|
|
461
|
+
}
|
|
462
|
+
}),
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
405
466
|
function resolveMemoryImportance(params: MemoryImportanceParams): number {
|
|
406
467
|
if (typeof params.explicitImportance === 'number') {
|
|
407
468
|
return clampImportance(params.explicitImportance)
|
|
@@ -412,6 +473,11 @@ function resolveMemoryImportance(params: MemoryImportanceParams): number {
|
|
|
412
473
|
}
|
|
413
474
|
|
|
414
475
|
const normalizedText = normalizeMemoryKey(params.text)
|
|
476
|
+
const extractedImportance = params.extractedImportanceByKey.get(normalizedText)
|
|
477
|
+
if (extractedImportance !== undefined) {
|
|
478
|
+
return clampImportance(extractedImportance)
|
|
479
|
+
}
|
|
480
|
+
|
|
415
481
|
const aiConfidence = params.confidenceByKey.get(normalizedText)
|
|
416
482
|
if (aiConfidence !== undefined) {
|
|
417
483
|
const durability = params.durabilityByKey.get(normalizedText) ?? 'standard'
|
|
@@ -429,6 +495,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
|
|
|
429
495
|
updates: MemoryUpdateOutputLike<TRelation>
|
|
430
496
|
memoryType: string
|
|
431
497
|
explicitImportance?: number
|
|
498
|
+
extractedImportanceByKey: Map<string, number>
|
|
432
499
|
confidenceByKey: Map<string, number>
|
|
433
500
|
durabilityByKey: Map<string, string>
|
|
434
501
|
categoryByKey: Map<string, string | undefined>
|
|
@@ -502,6 +569,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
|
|
|
502
569
|
text,
|
|
503
570
|
memoryType: params.memoryType,
|
|
504
571
|
explicitImportance: params.explicitImportance,
|
|
572
|
+
extractedImportanceByKey: params.extractedImportanceByKey,
|
|
505
573
|
confidenceByKey: params.confidenceByKey,
|
|
506
574
|
durabilityByKey: params.durabilityByKey,
|
|
507
575
|
categoryByKey: params.categoryByKey,
|
|
@@ -524,6 +592,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
|
|
|
524
592
|
text,
|
|
525
593
|
memoryType: params.memoryType,
|
|
526
594
|
explicitImportance: params.explicitImportance,
|
|
595
|
+
extractedImportanceByKey: params.extractedImportanceByKey,
|
|
527
596
|
confidenceByKey: params.confidenceByKey,
|
|
528
597
|
durabilityByKey: params.durabilityByKey,
|
|
529
598
|
categoryByKey: params.categoryByKey,
|
|
@@ -17,12 +17,23 @@ Classification:
|
|
|
17
17
|
- "preference": Subjective preference or style choice.
|
|
18
18
|
- "decision": Explicit decision, requirement, or policy.
|
|
19
19
|
|
|
20
|
+
Storage scoring:
|
|
21
|
+
- importance: 0 to 1 for long-term usefulness.
|
|
22
|
+
- classification: durable | transient | uncertain.
|
|
23
|
+
- rationale: short and concrete.
|
|
24
|
+
|
|
20
25
|
Confidence scoring:
|
|
21
26
|
- 1.0: Explicit, confirmed statements ("We use TypeScript", "I prefer dark mode", "We decided to migrate").
|
|
22
27
|
- 0.8: Strong implications or clearly implied facts.
|
|
23
28
|
- 0.6-0.7: Reasonable inferences with some ambiguity.
|
|
24
29
|
- Below 0.6: Do NOT extract. Skip entirely.
|
|
25
30
|
|
|
31
|
+
Importance scoring:
|
|
32
|
+
- 0.9-1.0: Foundational business decisions, architecture, durable requirements, critical facts.
|
|
33
|
+
- 0.6-0.8: Stable process or domain knowledge worth reusing later.
|
|
34
|
+
- 0.2-0.5: Low-value or weakly durable memory.
|
|
35
|
+
- Use classification="transient" for low-value facts that should usually be skipped.
|
|
36
|
+
|
|
26
37
|
Durability classification:
|
|
27
38
|
- "core": Business decisions, technical architecture choices, confirmed requirements, strategic direction. These are foundational and rarely change. Examples: "We use SurrealDB", "We decided to adopt microservices", "Revenue target is $1M ARR".
|
|
28
39
|
- "standard": General facts, moderate inferences, process details. These may evolve over time. Examples: "Team has 5 engineers", "Sprint cycle is 2 weeks", "Using Jest for testing".
|
|
@@ -33,10 +44,15 @@ Hard rules:
|
|
|
33
44
|
- Do NOT extract transient tasks, questions, research requests, or one-off conversation details.
|
|
34
45
|
- Do NOT turn suggestions, brainstorming, or hypotheticals into facts. If something is not confirmed, skip it.
|
|
35
46
|
- Do NOT trust new facts introduced by the agent unless the user explicitly confirms them.
|
|
47
|
+
- Use classification="transient" only when the fact is not worth persisting long-term.
|
|
36
48
|
- Preserve concrete numeric literals and units exactly when present (examples: "6", "50k", "$79", "15%", "Q2 2026").
|
|
37
49
|
- If a statement explicitly marks an older policy/value as deprecated or obsolete, capture both:
|
|
38
50
|
1) the current truth
|
|
39
51
|
2) the deprecation/obsolescence fact
|
|
52
|
+
- For every returned fact, also provide:
|
|
53
|
+
- importance: 0 to 1
|
|
54
|
+
- classification: durable | transient | uncertain
|
|
55
|
+
- rationale: one short sentence
|
|
40
56
|
- Prefer returning fewer items. If uncertain, return an empty list.
|
|
41
57
|
- Max ${maxFacts} facts.
|
|
42
58
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pluginRuntime } from '../config/agent-defaults'
|
|
2
|
+
import type { LotaRuntimeIndexedRepositoriesContext } from './runtime-extensions'
|
|
2
3
|
import { getRuntimeAdapters } from './runtime-extensions'
|
|
3
4
|
|
|
4
5
|
export function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
|
|
@@ -20,8 +21,8 @@ export function getPluginService(path: string[]): ((...args: unknown[]) => unkno
|
|
|
20
21
|
|
|
21
22
|
export async function buildIndexedRepositoriesContext(
|
|
22
23
|
organizationId: string,
|
|
23
|
-
): Promise<
|
|
24
|
-
const buildContext = getRuntimeAdapters().
|
|
24
|
+
): Promise<LotaRuntimeIndexedRepositoriesContext> {
|
|
25
|
+
const buildContext = getRuntimeAdapters().buildIndexedRepositoriesContext
|
|
25
26
|
if (!buildContext) {
|
|
26
27
|
return { provideRepoTool: false, defaultSectionsByAgent: {}, context: '' }
|
|
27
28
|
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CarryForwardPolicy,
|
|
3
|
-
CycleSchedule,
|
|
4
|
-
PlanDraft,
|
|
5
|
-
PlanNodeResult,
|
|
6
|
-
PlanNodeSpec,
|
|
7
|
-
PlanScheduleSpec,
|
|
8
|
-
SignalDeclaration,
|
|
9
|
-
} from '@lota-sdk/shared'
|
|
1
|
+
import type { PlanNodeResult, PlanNodeSpec } from '@lota-sdk/shared'
|
|
10
2
|
|
|
11
3
|
import type { RecordIdRef } from '../db/record-id'
|
|
12
4
|
|
|
13
|
-
export interface PluginContextEnricher {
|
|
14
|
-
domain: string
|
|
15
|
-
enrich(params: {
|
|
16
|
-
objective: string
|
|
17
|
-
organizationId: string
|
|
18
|
-
}): Promise<{ data: Record<string, unknown>; confidence: number }>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
5
|
export interface LotaPluginContributions {
|
|
22
6
|
envKeys: readonly string[]
|
|
23
7
|
schemaFiles: readonly (string | URL)[]
|
|
24
|
-
signals?: readonly SignalDeclaration[]
|
|
25
8
|
}
|
|
26
9
|
|
|
27
10
|
export interface PluginNodeExecutorContext {
|
|
@@ -50,33 +33,9 @@ export interface SystemNodeExecutor {
|
|
|
50
33
|
executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult>
|
|
51
34
|
}
|
|
52
35
|
|
|
53
|
-
export interface PlaybookContribution {
|
|
54
|
-
name: string
|
|
55
|
-
description: string
|
|
56
|
-
tags: string[]
|
|
57
|
-
draft: PlanDraft
|
|
58
|
-
schedule?: PlanScheduleSpec
|
|
59
|
-
cycleSchedule?: CycleSchedule
|
|
60
|
-
carryForwardPolicy?: CarryForwardPolicy
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface PlaybookContributor {
|
|
64
|
-
playbooks: readonly PlaybookContribution[]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface PluginDomainAgentDefinition {
|
|
68
|
-
agentId: string
|
|
69
|
-
displayName: string
|
|
70
|
-
capabilities: readonly string[]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
36
|
export interface LotaPlugin<TServices = Record<string, unknown>, TTools = Record<string, unknown>> {
|
|
74
37
|
services: TServices
|
|
75
38
|
nodeExecutor?: PluginNodeExecutor
|
|
76
39
|
tools?: TTools
|
|
77
40
|
contributions: LotaPluginContributions
|
|
78
|
-
playbookContributor?: PlaybookContributor
|
|
79
|
-
domainAgents?: readonly PluginDomainAgentDefinition[]
|
|
80
|
-
contextEnrichers?: readonly PluginContextEnricher[]
|
|
81
|
-
onSignal?: (signal: string, payload: unknown, source: string) => Promise<void> | void
|
|
82
41
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { agentDisplayNames } from '../config/agent-defaults'
|
|
4
|
+
import type { RecordIdRef } from '../db/record-id'
|
|
5
|
+
import { enqueueMemoryConsolidation } from '../queues/memory-consolidation.queue'
|
|
6
|
+
import { enqueueRegularChatMemoryDigest, enqueueSkillExtraction } from '../queues/organization-learning.queue'
|
|
7
|
+
import { enqueuePostChatMemory } from '../queues/post-chat-memory.queue'
|
|
8
|
+
import { enqueueRecentActivityTitleRefinement } from '../queues/title-generation.queue'
|
|
9
|
+
import {
|
|
10
|
+
shouldEnqueueMemoryConsolidation,
|
|
11
|
+
shouldEnqueueMemoryExtraction,
|
|
12
|
+
shouldEnqueueOnboardingPostChatMemory,
|
|
13
|
+
shouldEnqueueRegularDigestForWorkstream,
|
|
14
|
+
} from '../runtime/memory-digest-policy'
|
|
15
|
+
import { getRuntimeAdapters } from '../runtime/runtime-extensions'
|
|
16
|
+
import { shouldEnqueueSkillExtraction } from '../runtime/skill-extraction-policy'
|
|
17
|
+
import {
|
|
18
|
+
appendPersistedWorkstreamContextToHistoryMessages,
|
|
19
|
+
buildAgentHistoryMessages,
|
|
20
|
+
buildConversationSummary,
|
|
21
|
+
buildReadableUploadMetadataContext,
|
|
22
|
+
extractMessageText,
|
|
23
|
+
readOptionalString,
|
|
24
|
+
toHistoryMessages,
|
|
25
|
+
} from '../runtime/workstream-chat-helpers'
|
|
26
|
+
import { recentActivityService } from '../services/recent-activity.service'
|
|
27
|
+
import { workstreamService } from '../services/workstream.service'
|
|
28
|
+
import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstream.types'
|
|
29
|
+
import { safeEnqueue } from '../utils/async'
|
|
30
|
+
import { toIsoDateTimeString } from '../utils/date-time'
|
|
31
|
+
import type { WorkstreamState } from './workstream-state'
|
|
32
|
+
|
|
33
|
+
function buildRecentActivityChatDeepLink(params: {
|
|
34
|
+
workstream: NormalizedWorkstream
|
|
35
|
+
workstreamId: string
|
|
36
|
+
visibleAgentId: string
|
|
37
|
+
}): { route: string; search: Record<string, string> } {
|
|
38
|
+
if (params.workstream.mode === 'direct') {
|
|
39
|
+
return { route: 'direct-workstream', search: { workstreamId: params.workstreamId, agentId: params.visibleAgentId } }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { route: 'group-workstream', search: { workstreamId: params.workstreamId } }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildRecentActivityChatSystemTitle(params: {
|
|
46
|
+
workstream: NormalizedWorkstream
|
|
47
|
+
visibleAgentId: string
|
|
48
|
+
}): string {
|
|
49
|
+
if (params.workstream.mode === 'direct') {
|
|
50
|
+
return `Conversation with ${agentDisplayNames[params.visibleAgentId]}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return params.workstream.title.trim() || 'Workstream update'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface PostTurnSideEffectsParams {
|
|
57
|
+
workstream: NormalizedWorkstream
|
|
58
|
+
workstreamRef: RecordIdRef
|
|
59
|
+
orgRef: RecordIdRef
|
|
60
|
+
userRef: RecordIdRef
|
|
61
|
+
userName?: string | null
|
|
62
|
+
orgIdString: string
|
|
63
|
+
workstreamIdString: string
|
|
64
|
+
onboardingActive: boolean
|
|
65
|
+
workspace: unknown
|
|
66
|
+
allAssistantMessages: ChatMessage[]
|
|
67
|
+
referenceUserMessage: ChatMessage | undefined
|
|
68
|
+
referenceUserMessageId: string
|
|
69
|
+
loadRecentHistory: () => Promise<ChatMessage[]>
|
|
70
|
+
listReadableUploads: () => Array<{
|
|
71
|
+
filename: string
|
|
72
|
+
mediaType: string
|
|
73
|
+
sizeBytes: number | null
|
|
74
|
+
storageKey: string
|
|
75
|
+
}>
|
|
76
|
+
memoryBlock: string
|
|
77
|
+
visibleWorkstreamAgentId: string | null | undefined
|
|
78
|
+
defaultLeadAgentId: string
|
|
79
|
+
latestWorkstreamRecord: WorkstreamRecord
|
|
80
|
+
latestPersistedState: WorkstreamState | null
|
|
81
|
+
isUserTurn: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function runPostTurnSideEffects(params: PostTurnSideEffectsParams): Promise<void> {
|
|
85
|
+
const recentHistory = await params.loadRecentHistory()
|
|
86
|
+
const turnCount = await workstreamService.incrementTurnCount(params.workstreamRef)
|
|
87
|
+
const agentMessages = buildAgentHistoryMessages(params.allAssistantMessages)
|
|
88
|
+
const historyMessagesForMemory = appendPersistedWorkstreamContextToHistoryMessages(toHistoryMessages(recentHistory), {
|
|
89
|
+
compactionSummary: params.latestWorkstreamRecord.compactionSummary,
|
|
90
|
+
persistedState: params.latestPersistedState,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const userMessageText = params.referenceUserMessage ? extractMessageText(params.referenceUserMessage).trim() : ''
|
|
94
|
+
const readableUploads = params.listReadableUploads()
|
|
95
|
+
const attachmentMetadataContext = buildReadableUploadMetadataContext(readableUploads)
|
|
96
|
+
const hasAttachmentContext = Boolean(attachmentMetadataContext)
|
|
97
|
+
const shouldExtractMemory = params.onboardingActive
|
|
98
|
+
? shouldEnqueueOnboardingPostChatMemory({
|
|
99
|
+
onboardingActive: params.onboardingActive,
|
|
100
|
+
userMessageText,
|
|
101
|
+
hasAttachmentContext,
|
|
102
|
+
agentMessageCount: agentMessages.length,
|
|
103
|
+
})
|
|
104
|
+
: shouldEnqueueMemoryExtraction({ onboardingActive: params.onboardingActive, turnCount }) &&
|
|
105
|
+
userMessageText.length > 0
|
|
106
|
+
|
|
107
|
+
if (shouldExtractMemory) {
|
|
108
|
+
const memoryUserMessage = userMessageText || 'User uploaded attachment(s).'
|
|
109
|
+
await safeEnqueue(
|
|
110
|
+
() =>
|
|
111
|
+
enqueuePostChatMemory({
|
|
112
|
+
orgId: params.orgIdString,
|
|
113
|
+
workstreamId: params.workstreamIdString,
|
|
114
|
+
sourceId: params.referenceUserMessageId,
|
|
115
|
+
onboardStatus: readOptionalString((params.workspace as { onboardStatus?: unknown }).onboardStatus),
|
|
116
|
+
userMessage: memoryUserMessage,
|
|
117
|
+
historyMessages: historyMessagesForMemory,
|
|
118
|
+
agentMessages,
|
|
119
|
+
memoryBlock: params.memoryBlock.trim() ? params.memoryBlock : undefined,
|
|
120
|
+
attachmentContext: attachmentMetadataContext,
|
|
121
|
+
}),
|
|
122
|
+
{ operationName: 'post-chat memory extraction enqueue' },
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (params.isUserTurn && params.referenceUserMessage) {
|
|
127
|
+
const conversationSummary = buildConversationSummary({
|
|
128
|
+
userMessageText,
|
|
129
|
+
assistantMessages: params.allAssistantMessages,
|
|
130
|
+
})
|
|
131
|
+
if (conversationSummary) {
|
|
132
|
+
const effectiveAgentId = params.visibleWorkstreamAgentId ?? params.defaultLeadAgentId
|
|
133
|
+
const recentActivityResult = await recentActivityService.recordEvent({
|
|
134
|
+
orgId: params.orgRef,
|
|
135
|
+
userId: params.userRef,
|
|
136
|
+
source: 'system',
|
|
137
|
+
event: {
|
|
138
|
+
sourceEventId: `chat-turn:${params.referenceUserMessageId}`,
|
|
139
|
+
kind: 'chat.turn.completed',
|
|
140
|
+
targetKind: 'workstream',
|
|
141
|
+
targetId: params.workstreamIdString,
|
|
142
|
+
mergeKey: `workstream:${params.workstreamIdString}`,
|
|
143
|
+
title: buildRecentActivityChatSystemTitle({
|
|
144
|
+
workstream: params.workstream,
|
|
145
|
+
visibleAgentId: effectiveAgentId,
|
|
146
|
+
}),
|
|
147
|
+
sourceLabel: agentDisplayNames[effectiveAgentId],
|
|
148
|
+
deepLink: buildRecentActivityChatDeepLink({
|
|
149
|
+
workstream: params.workstream,
|
|
150
|
+
workstreamId: params.workstreamIdString,
|
|
151
|
+
visibleAgentId: effectiveAgentId,
|
|
152
|
+
}),
|
|
153
|
+
metadata: {
|
|
154
|
+
agentId: effectiveAgentId,
|
|
155
|
+
agentName: agentDisplayNames[effectiveAgentId],
|
|
156
|
+
workstreamId: params.workstreamIdString,
|
|
157
|
+
workstreamTitle: params.latestWorkstreamRecord.title ?? params.workstream.title,
|
|
158
|
+
workstreamMode: params.workstream.mode,
|
|
159
|
+
...(params.workstream.coreType ? { coreType: params.workstream.coreType } : {}),
|
|
160
|
+
userMessageText,
|
|
161
|
+
assistantSummary: conversationSummary,
|
|
162
|
+
messageId: params.referenceUserMessageId,
|
|
163
|
+
},
|
|
164
|
+
occurredAt: toIsoDateTimeString(params.referenceUserMessage.metadata?.createdAt ?? Date.now()),
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await safeEnqueue(
|
|
169
|
+
async () => {
|
|
170
|
+
const enqueuePostChatOrgAction = getRuntimeAdapters().enqueuePostChatOrgAction
|
|
171
|
+
if (!enqueuePostChatOrgAction) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const sourceCreatedAt = params.referenceUserMessage?.metadata?.createdAt ?? Date.now()
|
|
175
|
+
|
|
176
|
+
await enqueuePostChatOrgAction({
|
|
177
|
+
orgId: params.orgIdString,
|
|
178
|
+
workstreamId: params.workstreamIdString,
|
|
179
|
+
sourceId: params.referenceUserMessageId,
|
|
180
|
+
sourceCreatedAt,
|
|
181
|
+
conversationSummary,
|
|
182
|
+
})
|
|
183
|
+
},
|
|
184
|
+
{ operationName: 'post-chat org action enqueue' },
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if (recentActivityService.isMeaningfulRefinementCandidate(recentActivityResult.item)) {
|
|
188
|
+
await safeEnqueue(() => enqueueRecentActivityTitleRefinement({ activityId: recentActivityResult.item.id }), {
|
|
189
|
+
operationName: 'recent activity title refinement enqueue',
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (shouldEnqueueRegularDigestForWorkstream({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
196
|
+
await safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: params.orgIdString }), {
|
|
197
|
+
operationName: 'regular chat memory digest enqueue',
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (shouldEnqueueSkillExtraction({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
202
|
+
await safeEnqueue(() => enqueueSkillExtraction({ orgId: params.orgIdString }), {
|
|
203
|
+
operationName: 'skill extraction enqueue',
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (shouldEnqueueMemoryConsolidation({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
208
|
+
await safeEnqueue(() => enqueueMemoryConsolidation({ scopeId: params.orgIdString }), {
|
|
209
|
+
operationName: 'memory consolidation enqueue',
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -246,19 +246,6 @@ export const LotaRuntimeConfigSchema = z.object({
|
|
|
246
246
|
})
|
|
247
247
|
.default({ searchK: 6, embeddingCacheTtlSeconds: 7200 }),
|
|
248
248
|
workstreams: workstreamConfigSchema.default({}),
|
|
249
|
-
backgroundProcessing: z
|
|
250
|
-
.object({
|
|
251
|
-
memoryExtractionFrequency: z.coerce.number().int().positive().default(3),
|
|
252
|
-
skillExtractionFrequency: z.coerce.number().int().positive().default(5),
|
|
253
|
-
memoryDigestFrequency: z.coerce.number().int().positive().default(1),
|
|
254
|
-
memoryConsolidationFrequency: z.coerce.number().int().positive().default(10),
|
|
255
|
-
})
|
|
256
|
-
.default({
|
|
257
|
-
memoryExtractionFrequency: 3,
|
|
258
|
-
skillExtractionFrequency: 5,
|
|
259
|
-
memoryDigestFrequency: 1,
|
|
260
|
-
memoryConsolidationFrequency: 10,
|
|
261
|
-
}),
|
|
262
249
|
agents: agentsConfigSchema,
|
|
263
250
|
toolProviders: z.custom<Record<string, unknown>>(isToolProviderRecord).optional(),
|
|
264
251
|
extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
|
|
@@ -184,20 +184,14 @@ export interface LotaRuntimeTurnHooks {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
export interface LotaRuntimeAdapters {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
|
|
196
|
-
}
|
|
197
|
-
workers?: {
|
|
198
|
-
connectPluginDatabases?: () => Promise<void>
|
|
199
|
-
withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
|
|
200
|
-
}
|
|
187
|
+
workspaceProvider?: LotaRuntimeWorkspaceProvider
|
|
188
|
+
planEventAdapter?: LotaRuntimePlanEventAdapter
|
|
189
|
+
buildIndexedRepositoriesContext?: (workspaceId: string) => Promise<LotaRuntimeIndexedRepositoriesContext>
|
|
190
|
+
buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
|
|
191
|
+
enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
|
|
192
|
+
enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
|
|
193
|
+
connectPluginDatabases?: () => Promise<void>
|
|
194
|
+
withWorkspaceMemoryLock?: <T>(workspaceId: string, fn: () => Promise<T>) => Promise<T>
|
|
201
195
|
}
|
|
202
196
|
|
|
203
197
|
interface RuntimeExtensionsState {
|
|
@@ -243,11 +237,11 @@ export function getToolProviders(): ToolSet {
|
|
|
243
237
|
}
|
|
244
238
|
|
|
245
239
|
export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) | undefined {
|
|
246
|
-
return runtimeExtensionsState.adapters.
|
|
240
|
+
return runtimeExtensionsState.adapters.connectPluginDatabases
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
|
|
250
|
-
const adapter = runtimeExtensionsState.adapters.
|
|
244
|
+
const adapter = runtimeExtensionsState.adapters.withWorkspaceMemoryLock
|
|
251
245
|
if (!adapter) {
|
|
252
246
|
return fn()
|
|
253
247
|
}
|
|
@@ -2,31 +2,26 @@ import { startAutonomousJobWorker } from '../queues/autonomous-job.queue'
|
|
|
2
2
|
import { startContextCompactionWorker } from '../queues/context-compaction.queue'
|
|
3
3
|
import { startDelayedNodePromotionWorker } from '../queues/delayed-node-promotion.queue'
|
|
4
4
|
import { scheduleRecurringConsolidation, startMemoryConsolidationWorker } from '../queues/memory-consolidation.queue'
|
|
5
|
-
import {
|
|
5
|
+
import { startOrganizationLearningWorker } from '../queues/organization-learning.queue'
|
|
6
|
+
import { startPlanAgentHeartbeatWorker } from '../queues/plan-agent-heartbeat.queue'
|
|
6
7
|
import { startPlanSchedulerWorker } from '../queues/plan-scheduler.queue'
|
|
7
8
|
import { startPostChatMemoryWorker } from '../queues/post-chat-memory.queue'
|
|
8
|
-
import {
|
|
9
|
-
import { startRegularChatMemoryDigestWorker } from '../queues/regular-chat-memory-digest.queue'
|
|
10
|
-
import { startSkillExtractionWorker } from '../queues/skill-extraction.queue'
|
|
11
|
-
import { startWorkstreamTitleGenerationWorker } from '../queues/workstream-title-generation.queue'
|
|
9
|
+
import { startTitleGenerationWorker } from '../queues/title-generation.queue'
|
|
12
10
|
|
|
13
11
|
export interface LotaRuntimeWorkerStartRegistry {
|
|
14
12
|
autonomousJob: typeof startAutonomousJobWorker
|
|
15
13
|
contextCompaction: typeof startContextCompactionWorker
|
|
16
14
|
delayedNodePromotion: typeof startDelayedNodePromotionWorker
|
|
17
15
|
memoryConsolidation: typeof startMemoryConsolidationWorker
|
|
16
|
+
organizationLearning: typeof startOrganizationLearningWorker
|
|
18
17
|
planAgentHeartbeat: typeof startPlanAgentHeartbeatWorker
|
|
19
18
|
planScheduler: typeof startPlanSchedulerWorker
|
|
20
19
|
postChatMemory: typeof startPostChatMemoryWorker
|
|
21
|
-
|
|
22
|
-
skillExtraction: typeof startSkillExtractionWorker
|
|
23
|
-
workstreamTitleGeneration: typeof startWorkstreamTitleGenerationWorker
|
|
24
|
-
recentActivityTitleRefinement: typeof startRecentActivityTitleRefinementWorker
|
|
20
|
+
titleGeneration: typeof startTitleGenerationWorker
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
export interface LotaRuntimeWorkerScheduleRegistry {
|
|
28
24
|
recurringConsolidation: typeof scheduleRecurringConsolidation
|
|
29
|
-
planAgentHeartbeatSweep: typeof schedulePlanAgentHeartbeatSweep
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
export interface LotaRuntimeWorkers {
|
|
@@ -46,19 +41,13 @@ export function buildRuntimeWorkerRegistry(extraWorkers?: LotaRuntimeWorkerExten
|
|
|
46
41
|
contextCompaction: startContextCompactionWorker,
|
|
47
42
|
delayedNodePromotion: startDelayedNodePromotionWorker,
|
|
48
43
|
memoryConsolidation: startMemoryConsolidationWorker,
|
|
44
|
+
organizationLearning: startOrganizationLearningWorker,
|
|
49
45
|
planAgentHeartbeat: startPlanAgentHeartbeatWorker,
|
|
50
46
|
planScheduler: startPlanSchedulerWorker,
|
|
51
47
|
postChatMemory: startPostChatMemoryWorker,
|
|
52
|
-
|
|
53
|
-
skillExtraction: startSkillExtractionWorker,
|
|
54
|
-
workstreamTitleGeneration: startWorkstreamTitleGenerationWorker,
|
|
55
|
-
recentActivityTitleRefinement: startRecentActivityTitleRefinementWorker,
|
|
48
|
+
titleGeneration: startTitleGenerationWorker,
|
|
56
49
|
...extraWorkers?.start,
|
|
57
50
|
},
|
|
58
|
-
schedule: {
|
|
59
|
-
recurringConsolidation: scheduleRecurringConsolidation,
|
|
60
|
-
planAgentHeartbeatSweep: schedulePlanAgentHeartbeatSweep,
|
|
61
|
-
...extraWorkers?.schedule,
|
|
62
|
-
},
|
|
51
|
+
schedule: { recurringConsolidation: scheduleRecurringConsolidation, ...extraWorkers?.schedule },
|
|
63
52
|
}
|
|
64
53
|
}
|