@lota-sdk/core 0.1.14 → 0.1.15
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 +5 -5
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +1 -0
- package/src/bifrost/bifrost.ts +12 -7
- package/src/config/agent-defaults.ts +1 -1
- package/src/config/logger.ts +7 -9
- package/src/{runtime.ts → create-runtime.ts} +6 -6
- package/src/db/cursor-pagination.ts +1 -1
- package/src/db/memory-store.ts +10 -6
- package/src/db/memory.ts +6 -4
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +40 -43
- package/src/db/startup.ts +3 -3
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +4 -8
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/memory-consolidation.queue.ts +7 -8
- package/src/queues/post-chat-memory.queue.ts +2 -6
- package/src/queues/recent-activity-title-refinement.queue.ts +2 -6
- package/src/queues/regular-chat-memory-digest.queue.ts +4 -7
- package/src/queues/skill-extraction.queue.ts +4 -7
- package/src/queues/workstream-title-generation.queue.ts +2 -6
- package/src/redis/connection.ts +6 -3
- package/src/redis/index.ts +1 -0
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +6 -4
- package/src/runtime/context-compaction.ts +19 -38
- package/src/runtime/execution-plan.ts +2 -2
- package/src/runtime/helper-model.ts +3 -1
- package/src/runtime/index.ts +12 -1
- package/src/runtime/memory-block.ts +3 -2
- package/src/runtime/memory-pipeline.ts +24 -5
- package/src/runtime/plugin-types.ts +1 -1
- package/src/runtime/runtime-extensions.ts +89 -13
- package/src/runtime/title-helpers.ts +11 -2
- package/src/runtime/workstream-chat-helpers.ts +5 -6
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/attachment.service.ts +1 -1
- package/src/services/context-compaction.service.ts +3 -3
- package/src/services/document-chunk.service.ts +37 -32
- package/src/services/execution-plan.service.ts +2 -0
- package/src/services/learned-skill.service.ts +6 -10
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -8
- package/src/services/memory.service.ts +21 -18
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-executor.service.ts +2 -18
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-validator.service.ts +3 -18
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +6 -12
- package/src/services/workstream-message.service.ts +26 -16
- package/src/services/workstream-title.service.ts +1 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +401 -314
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +22 -10
- package/src/services/workstream.types.ts +7 -16
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +1 -4
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +3 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +6 -2
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -2
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +9 -5
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +10 -2
- package/src/utils/string.ts +14 -0
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +9 -103
- package/src/workers/skill-extraction.runner.ts +7 -101
- package/src/workers/utils/file-section-chunker.ts +5 -3
- package/src/workers/utils/workstream-message-query.ts +106 -0
- package/src/workers/worker-utils.ts +4 -0
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agentDisplayNames, agentShortDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
|
|
2
|
+
import { compactWhitespace } from '../utils/string'
|
|
2
3
|
|
|
3
4
|
function escapeRegex(value: string): string {
|
|
4
5
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
@@ -173,8 +174,7 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
|
|
|
173
174
|
)
|
|
174
175
|
.filter(Boolean)
|
|
175
176
|
|
|
176
|
-
const
|
|
177
|
-
const collapsed = candidate.replace(/\s+/g, ' ').trim()
|
|
177
|
+
const collapsed = compactWhitespace(normalizedLines.join(' '))
|
|
178
178
|
if (!collapsed) return ''
|
|
179
179
|
return collapsed
|
|
180
180
|
}
|
|
@@ -186,6 +186,7 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
|
|
|
186
186
|
try {
|
|
187
187
|
const parsed: unknown = JSON.parse(trimmed)
|
|
188
188
|
if (!Array.isArray(parsed)) return []
|
|
189
|
+
if (!parsed.every((item: unknown) => typeof item === 'object' && item !== null)) return []
|
|
189
190
|
return parsed as MemoryBlockEntry[]
|
|
190
191
|
} catch {
|
|
191
192
|
return []
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { compactWhitespace } from '../utils/string'
|
|
2
|
+
|
|
3
|
+
const SCORE_WEIGHTS = {
|
|
4
|
+
durability: { core: 0.35, standard: 0.2, weak: 0.05 },
|
|
5
|
+
type: { decision: 0.25, preference: 0.18, default: 0.1 },
|
|
6
|
+
maxContentLength: 120,
|
|
7
|
+
} as const
|
|
8
|
+
|
|
1
9
|
interface MemoryFactInput {
|
|
2
10
|
content: string
|
|
3
11
|
confidence: number
|
|
@@ -117,9 +125,20 @@ function scoreFact<T extends MemoryFactInput>(fact: T): number {
|
|
|
117
125
|
const durability = fact.durability ?? 'standard'
|
|
118
126
|
const type = fact.type ?? 'fact'
|
|
119
127
|
|
|
120
|
-
const durabilityWeight =
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
const durabilityWeight =
|
|
129
|
+
durability === 'core'
|
|
130
|
+
? SCORE_WEIGHTS.durability.core
|
|
131
|
+
: durability === 'standard'
|
|
132
|
+
? SCORE_WEIGHTS.durability.standard
|
|
133
|
+
: SCORE_WEIGHTS.durability.weak
|
|
134
|
+
const typeWeight =
|
|
135
|
+
type === 'decision'
|
|
136
|
+
? SCORE_WEIGHTS.type.decision
|
|
137
|
+
: type === 'fact'
|
|
138
|
+
? SCORE_WEIGHTS.type.preference
|
|
139
|
+
: SCORE_WEIGHTS.type.default
|
|
140
|
+
const lengthWeight =
|
|
141
|
+
Math.min(fact.content.length, SCORE_WEIGHTS.maxContentLength) / SCORE_WEIGHTS.maxContentLength / 10
|
|
123
142
|
|
|
124
143
|
return confidence + durabilityWeight + typeWeight + lengthWeight
|
|
125
144
|
}
|
|
@@ -160,7 +179,7 @@ export function postProcessMemoryFacts<T extends MemoryFactInput>(
|
|
|
160
179
|
const deduped = new Map<string, T>()
|
|
161
180
|
|
|
162
181
|
for (const fact of rawFacts) {
|
|
163
|
-
const content = typeof fact.content === 'string' ? fact.content
|
|
182
|
+
const content = typeof fact.content === 'string' ? compactWhitespace(fact.content) : ''
|
|
164
183
|
if (!content || content.length < minChars || content.length > maxChars) continue
|
|
165
184
|
const normalizedFact = { ...fact, content }
|
|
166
185
|
const key = normalizeFactForDedupe(content)
|
|
@@ -464,7 +483,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
|
|
|
464
483
|
}
|
|
465
484
|
|
|
466
485
|
for (const [index, item] of params.updates.memory.entries()) {
|
|
467
|
-
const text = typeof item.text === 'string' ? item.text
|
|
486
|
+
const text = typeof item.text === 'string' ? compactWhitespace(item.text) : ''
|
|
468
487
|
const itemId = typeof item.id === 'string' ? item.id.trim() : ''
|
|
469
488
|
|
|
470
489
|
switch (item.event) {
|
|
@@ -3,7 +3,7 @@ export interface LotaPluginContributions {
|
|
|
3
3
|
schemaFiles: readonly (string | URL)[]
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export interface LotaPlugin<TServices =
|
|
6
|
+
export interface LotaPlugin<TServices = Record<string, unknown>, TTools = Record<string, unknown>> {
|
|
7
7
|
services: TServices
|
|
8
8
|
tools?: TTools
|
|
9
9
|
contributions: LotaPluginContributions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ToolSet } from 'ai'
|
|
2
2
|
|
|
3
3
|
import type { RecordIdRef } from '../db/record-id'
|
|
4
|
-
import type { ReadableUploadMetadata } from '../
|
|
4
|
+
import type { ReadableUploadMetadata } from '../storage/attachment-types'
|
|
5
5
|
import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
|
|
6
6
|
|
|
7
7
|
export interface LotaRuntimeBackgroundCursor {
|
|
@@ -34,7 +34,7 @@ export interface LotaRuntimeWorkspaceProvider {
|
|
|
34
34
|
): Promise<LotaRuntimeWorkspaceLifecycleState> | LotaRuntimeWorkspaceLifecycleState
|
|
35
35
|
readProfileProjectionState?(
|
|
36
36
|
workspace: Record<string, unknown>,
|
|
37
|
-
): Promise<LotaRuntimeWorkspaceProjectionState |
|
|
37
|
+
): Promise<LotaRuntimeWorkspaceProjectionState | undefined> | LotaRuntimeWorkspaceProjectionState | undefined
|
|
38
38
|
buildPromptSummary?(workspaceId: RecordIdRef): Promise<string | undefined>
|
|
39
39
|
listRecentDomainEvents?(workspaceId: RecordIdRef, limit?: number): Promise<Array<Record<string, unknown>>>
|
|
40
40
|
hasActiveKnowledgeSources?(workspaceId: string): Promise<boolean>
|
|
@@ -76,11 +76,87 @@ export interface LotaRuntimeTeamThinkToolsParams {
|
|
|
76
76
|
toolProviders?: ToolSet
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
export interface BuildContextParams {
|
|
80
|
+
workstream: unknown
|
|
81
|
+
workstreamRef: RecordIdRef
|
|
82
|
+
orgRef: RecordIdRef
|
|
83
|
+
userRef: RecordIdRef
|
|
84
|
+
userName?: string | null
|
|
85
|
+
workspace: Record<string, unknown>
|
|
86
|
+
onboardingActive: boolean
|
|
87
|
+
messageText: string
|
|
88
|
+
linearInstalled: boolean
|
|
89
|
+
githubInstalled: boolean
|
|
90
|
+
indexedRepoContext: unknown
|
|
91
|
+
promptContext: unknown
|
|
92
|
+
workspaceLifecycleState: unknown
|
|
93
|
+
workspaceProfileState: unknown
|
|
94
|
+
promptSummary: string | undefined
|
|
95
|
+
recentDomainEvents: Array<Record<string, unknown>>
|
|
96
|
+
retrievedKnowledgeSection: string | undefined
|
|
97
|
+
[key: string]: unknown
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface AfterTurnParams {
|
|
101
|
+
workstream: unknown
|
|
102
|
+
workstreamRef: RecordIdRef
|
|
103
|
+
orgRef: RecordIdRef
|
|
104
|
+
userRef: RecordIdRef
|
|
105
|
+
userName?: string | null
|
|
106
|
+
onboardingActive: boolean
|
|
107
|
+
referenceUserMessage: unknown
|
|
108
|
+
assistantMessages: unknown[]
|
|
109
|
+
latestWorkstreamRecord: unknown
|
|
110
|
+
latestPersistedState: unknown
|
|
111
|
+
context: Record<string, unknown> | null
|
|
112
|
+
[key: string]: unknown
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ResolveAgentParams {
|
|
116
|
+
agentId: string
|
|
117
|
+
mode: string
|
|
118
|
+
workstream: unknown
|
|
119
|
+
workstreamRef: RecordIdRef
|
|
120
|
+
orgRef: RecordIdRef
|
|
121
|
+
userRef: RecordIdRef
|
|
122
|
+
userName?: string | null
|
|
123
|
+
onboardingActive: boolean
|
|
124
|
+
linearInstalled: boolean
|
|
125
|
+
githubInstalled: boolean
|
|
126
|
+
reasoningProfile: string
|
|
127
|
+
skills?: string[]
|
|
128
|
+
additionalInstructionSections?: string[]
|
|
129
|
+
context: Record<string, unknown> | null
|
|
130
|
+
[key: string]: unknown
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface BuildExtraInstructionSectionsParams {
|
|
134
|
+
workstream: unknown
|
|
135
|
+
workstreamRef: RecordIdRef
|
|
136
|
+
orgRef: RecordIdRef
|
|
137
|
+
userRef: RecordIdRef
|
|
138
|
+
userName?: string | null
|
|
139
|
+
workspace: Record<string, unknown>
|
|
140
|
+
onboardingActive: boolean
|
|
141
|
+
messageText: string
|
|
142
|
+
linearInstalled: boolean
|
|
143
|
+
githubInstalled: boolean
|
|
144
|
+
indexedRepoContext: unknown
|
|
145
|
+
promptContext: unknown
|
|
146
|
+
workspaceLifecycleState: unknown
|
|
147
|
+
workspaceProfileState: unknown
|
|
148
|
+
promptSummary: string | undefined
|
|
149
|
+
recentDomainEvents: Array<Record<string, unknown>>
|
|
150
|
+
retrievedKnowledgeSection: string | undefined
|
|
151
|
+
context: Record<string, unknown> | null
|
|
152
|
+
[key: string]: unknown
|
|
153
|
+
}
|
|
154
|
+
|
|
79
155
|
export interface LotaRuntimeTurnHooks {
|
|
80
|
-
buildContext?: (params:
|
|
81
|
-
afterTurn?: (params:
|
|
82
|
-
resolveAgent?: (params:
|
|
83
|
-
buildExtraInstructionSections?: (params:
|
|
156
|
+
buildContext?: (params: BuildContextParams) => Promise<Record<string, unknown> | void>
|
|
157
|
+
afterTurn?: (params: AfterTurnParams) => Promise<void>
|
|
158
|
+
resolveAgent?: (params: ResolveAgentParams) => Promise<Record<string, unknown> | void>
|
|
159
|
+
buildExtraInstructionSections?: (params: BuildExtraInstructionSectionsParams) => Promise<string[] | void>
|
|
84
160
|
}
|
|
85
161
|
|
|
86
162
|
export interface LotaRuntimeAdapters {
|
|
@@ -90,8 +166,8 @@ export interface LotaRuntimeAdapters {
|
|
|
90
166
|
buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
|
|
91
167
|
}
|
|
92
168
|
queues?: {
|
|
93
|
-
enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
|
|
94
|
-
enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
|
|
169
|
+
enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
|
|
170
|
+
enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
|
|
95
171
|
}
|
|
96
172
|
workers?: {
|
|
97
173
|
connectPluginDatabases?: () => Promise<void>
|
|
@@ -115,17 +191,17 @@ let runtimeExtensionsState: RuntimeExtensionsState = {
|
|
|
115
191
|
extraWorkers: {},
|
|
116
192
|
}
|
|
117
193
|
|
|
118
|
-
export function configureRuntimeExtensions(params
|
|
194
|
+
export function configureRuntimeExtensions(params: {
|
|
119
195
|
adapters?: LotaRuntimeAdapters
|
|
120
196
|
turnHooks?: LotaRuntimeTurnHooks
|
|
121
197
|
toolProviders?: ToolSet
|
|
122
198
|
extraWorkers?: LotaRuntimeWorkerExtensions
|
|
123
199
|
}): void {
|
|
124
200
|
runtimeExtensionsState = {
|
|
125
|
-
adapters: params
|
|
126
|
-
turnHooks: params
|
|
127
|
-
toolProviders: params
|
|
128
|
-
extraWorkers: params
|
|
201
|
+
adapters: params.adapters ?? {},
|
|
202
|
+
turnHooks: params.turnHooks ?? {},
|
|
203
|
+
toolProviders: params.toolProviders ?? EMPTY_TOOLS,
|
|
204
|
+
extraWorkers: params.extraWorkers ?? {},
|
|
129
205
|
}
|
|
130
206
|
}
|
|
131
207
|
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
import { compactWhitespace } from '../utils/string'
|
|
2
|
+
|
|
1
3
|
const TITLE_WORD_LIMIT = 5
|
|
2
4
|
|
|
3
5
|
export function limitTitleWords(text: string): string {
|
|
4
|
-
const words = text
|
|
6
|
+
const words = compactWhitespace(text).split(' ').filter(Boolean)
|
|
5
7
|
return words.slice(0, TITLE_WORD_LIMIT).join(' ')
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export function deriveTitle(text: string): string {
|
|
9
|
-
const trimmed = text
|
|
11
|
+
const trimmed = compactWhitespace(text)
|
|
10
12
|
if (trimmed.length <= 60) return trimmed
|
|
11
13
|
return `${trimmed.slice(0, 57)}...`
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
export function normalizeTitle(value: string): string {
|
|
17
|
+
const normalized = compactWhitespace(value)
|
|
18
|
+
.replace(/^["'`]+|["'`]+$/g, '')
|
|
19
|
+
.replace(/[.!?,;:]+$/g, '')
|
|
20
|
+
return normalized.length <= 80 ? normalized : normalized.slice(0, 80).trim()
|
|
21
|
+
}
|
|
@@ -114,14 +114,13 @@ export function collectToolOutputErrors(params: {
|
|
|
114
114
|
if (typeof part !== 'object') continue
|
|
115
115
|
if (part.type !== undefined && typeof part.type !== 'string') continue
|
|
116
116
|
if (!part.type?.startsWith('tool-')) continue
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
const p = part as Record<string, unknown>
|
|
119
|
+
if (p.state !== 'output-error') continue
|
|
118
120
|
|
|
119
121
|
const toolName = part.type.slice('tool-'.length) || 'unknown'
|
|
120
|
-
const toolCallId =
|
|
121
|
-
|
|
122
|
-
? ((part as Record<string, unknown>).toolCallId as string)
|
|
123
|
-
: 'unknown'
|
|
124
|
-
const errorTextRaw = (part as Record<string, unknown>).errorText
|
|
122
|
+
const toolCallId = typeof p.toolCallId === 'string' && p.toolCallId ? p.toolCallId : 'unknown'
|
|
123
|
+
const errorTextRaw = p.errorText
|
|
125
124
|
const errorText =
|
|
126
125
|
typeof errorTextRaw === 'string' && errorTextRaw.trim()
|
|
127
126
|
? errorTextRaw.trim()
|
|
@@ -36,36 +36,6 @@ export function uniqueMentionOrder(message: string): string[] {
|
|
|
36
36
|
return ordered
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const GTM_STRONG_INTENT_PATTERNS: RegExp[] = [
|
|
40
|
-
/\bgo[-\s]?to[-\s]?market\b/i,
|
|
41
|
-
/\bgtm\b/i,
|
|
42
|
-
/\bcontent\s+marketing\b/i,
|
|
43
|
-
/\bcontent\s+strategy\b/i,
|
|
44
|
-
/\bdemand\s+generation\b/i,
|
|
45
|
-
/\bdistribution\s+strategy\b/i,
|
|
46
|
-
/\bpositioning\s+strategy\b/i,
|
|
47
|
-
/\blaunch\s+strategy\b/i,
|
|
48
|
-
/\blaunch\s+plan\b/i,
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
const GTM_WEAK_INTENT_PATTERNS: RegExp[] = [/\blaunch\b/i, /\bcommunity\b/i, /\bdistribution\b/i, /\bpositioning\b/i]
|
|
52
|
-
|
|
53
|
-
export function isGtmIntentMessage(message: string): boolean {
|
|
54
|
-
const text = message.trim()
|
|
55
|
-
if (!text) return false
|
|
56
|
-
|
|
57
|
-
if (GTM_STRONG_INTENT_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
58
|
-
return true
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let weakMatches = 0
|
|
62
|
-
for (const pattern of GTM_WEAK_INTENT_PATTERNS) {
|
|
63
|
-
if (pattern.test(text)) weakMatches += 1
|
|
64
|
-
if (weakMatches >= 2) return true
|
|
65
|
-
}
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
|
|
69
39
|
const HIGH_IMPACT_CLASS_PATTERNS: Array<{ className: HighImpactResponseClass; patterns: RegExp[] }> = [
|
|
70
40
|
{
|
|
71
41
|
className: 'architecture-recommendation',
|
|
@@ -238,13 +238,23 @@ export interface CompactionOutput {
|
|
|
238
238
|
stateDelta: WorkstreamStateDelta
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
const WORKSTREAM_STATE_MAX_KEY_DECISIONS = 8
|
|
242
|
+
const WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS = 6
|
|
243
|
+
const WORKSTREAM_STATE_MAX_TASKS = 10
|
|
244
|
+
const WORKSTREAM_STATE_MAX_OPEN_QUESTIONS = 5
|
|
245
|
+
const WORKSTREAM_STATE_MAX_RISKS = 5
|
|
246
|
+
const WORKSTREAM_STATE_MAX_ARTIFACTS = 10
|
|
247
|
+
const WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS = 6
|
|
248
|
+
|
|
249
|
+
export function applyWorkstreamStateCaps(state: WorkstreamState): void {
|
|
250
|
+
state.keyDecisions = state.keyDecisions.slice(-WORKSTREAM_STATE_MAX_KEY_DECISIONS)
|
|
251
|
+
state.activeConstraints = state.activeConstraints.slice(-WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS)
|
|
252
|
+
state.tasks = state.tasks.slice(-WORKSTREAM_STATE_MAX_TASKS)
|
|
253
|
+
state.openQuestions = state.openQuestions.slice(-WORKSTREAM_STATE_MAX_OPEN_QUESTIONS)
|
|
254
|
+
state.risks = state.risks.slice(-WORKSTREAM_STATE_MAX_RISKS)
|
|
255
|
+
state.artifacts = state.artifacts.slice(-WORKSTREAM_STATE_MAX_ARTIFACTS)
|
|
256
|
+
state.agentContributions = state.agentContributions.slice(-WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS)
|
|
257
|
+
}
|
|
248
258
|
|
|
249
259
|
export function createEmptyWorkstreamState(now = Date.now()): WorkstreamState {
|
|
250
260
|
return {
|
|
@@ -3,7 +3,7 @@ import { recordIdToString } from '../db/record-id'
|
|
|
3
3
|
import { TABLES } from '../db/tables'
|
|
4
4
|
import { attachmentStorageService } from '../storage/attachment-storage.service'
|
|
5
5
|
import type { UploadedWorkstreamAttachment as SdkUploadedWorkstreamAttachment } from '../storage/attachment-storage.service'
|
|
6
|
-
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/
|
|
6
|
+
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
|
|
7
7
|
|
|
8
8
|
export type ReadableUploadMetadata = SdkReadableUploadMetadata
|
|
9
9
|
|
|
@@ -6,9 +6,9 @@ import { recordIdToString } from '../db/record-id'
|
|
|
6
6
|
import { databaseService } from '../db/service'
|
|
7
7
|
import { TABLES } from '../db/tables'
|
|
8
8
|
import { parseWorkstreamState, toStateFieldsUpdated } from '../runtime/context-compaction'
|
|
9
|
-
import {
|
|
9
|
+
import { CONTEXT_WINDOW_TOKENS, WORKSTREAM_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
|
|
10
10
|
import type { WorkstreamState } from '../runtime/workstream-state'
|
|
11
|
-
import { compactMemoryBlockSummary, contextCompactionRuntime } from './context-compaction-runtime'
|
|
11
|
+
import { compactMemoryBlockSummary, contextCompactionRuntime } from './context-compaction-runtime.singleton'
|
|
12
12
|
import { workstreamMessageService } from './workstream-message.service'
|
|
13
13
|
import { WorkstreamSchema } from './workstream.types'
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@ class ContextCompactionService {
|
|
|
35
35
|
return contextCompactionRuntime.formatWorkstreamStateForPrompt(state)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
estimateThreshold(contextSize =
|
|
38
|
+
estimateThreshold(contextSize = CONTEXT_WINDOW_TOKENS): number {
|
|
39
39
|
return contextCompactionRuntime.estimateThreshold(contextSize)
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto'
|
|
|
3
3
|
import { chunkMarkdownDocument, chunkPagedDocument, chunkPlainTextDocument } from '../document/org-document-chunking'
|
|
4
4
|
import type { ParsedDocumentChunk } from '../document/org-document-chunking'
|
|
5
5
|
import { getDefaultEmbeddings } from '../embeddings/provider'
|
|
6
|
+
import { CHARS_PER_TOKEN_ESTIMATE } from '../utils/string'
|
|
6
7
|
|
|
7
8
|
type DocumentChunkEmbeddings = {
|
|
8
9
|
embedDocuments(documents: string[]): Promise<number[][]>
|
|
@@ -58,8 +59,10 @@ export class DocumentChunkService {
|
|
|
58
59
|
return createHash('sha256').update(content).digest('hex')
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// Uses 4 chars/token (conservative estimate for document content which tends
|
|
63
|
+
// to have longer words than conversational text where 3 chars/token is used).
|
|
61
64
|
estimateTokenCount(content: string): number {
|
|
62
|
-
return Math.max(1, Math.ceil(content.length /
|
|
65
|
+
return Math.max(1, Math.ceil(content.length / (CHARS_PER_TOKEN_ESTIMATE + 1)))
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
async embedQuery(query: string): Promise<number[]> {
|
|
@@ -95,37 +98,39 @@ export class DocumentChunkService {
|
|
|
95
98
|
|
|
96
99
|
await params.archive(staleVersionRows)
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
101
|
+
await Promise.all(
|
|
102
|
+
params.chunks.map(async (chunk, index) => {
|
|
103
|
+
const contentHash = this.hashContent(chunk.content)
|
|
104
|
+
const existingRow = existingByChunkKey.get(chunk.chunkKey)
|
|
105
|
+
const payload = params.buildPayload({
|
|
106
|
+
chunk,
|
|
107
|
+
embedding: embeddings[index] ?? [],
|
|
108
|
+
contentHash,
|
|
109
|
+
tokenEstimate: this.estimateTokenCount(chunk.content),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
seenChunkKeys.add(chunk.chunkKey)
|
|
113
|
+
|
|
114
|
+
if (!existingRow) {
|
|
115
|
+
await params.create(payload)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const current = params.selectShape(existingRow)
|
|
120
|
+
const hasChanged =
|
|
121
|
+
current.contentHash !== contentHash ||
|
|
122
|
+
current.chunkIndex !== chunk.chunkIndex ||
|
|
123
|
+
(current.sectionPath ?? null) !== (chunk.sectionPath ?? null) ||
|
|
124
|
+
(current.pageStart ?? null) !== (chunk.pageStart ?? null) ||
|
|
125
|
+
(current.pageEnd ?? null) !== (chunk.pageEnd ?? null)
|
|
126
|
+
|
|
127
|
+
if (!hasChanged) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await params.update(existingRow, payload)
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
129
134
|
|
|
130
135
|
const removedCurrentVersionRows = existingRows.filter((row) => {
|
|
131
136
|
const current = params.selectShape(row)
|
|
@@ -612,6 +612,7 @@ class ExecutionPlanService {
|
|
|
612
612
|
): Promise<PlanNodeSpecRecord[]> {
|
|
613
613
|
const createdRecords: PlanNodeSpecRecord[] = []
|
|
614
614
|
|
|
615
|
+
// Sequential: SurrealDB transactions require ordered operations
|
|
615
616
|
for (const compiledNode of nodes) {
|
|
616
617
|
const nodeSpecId = new RecordId(TABLES.PLAN_NODE_SPEC, Bun.randomUUIDv7())
|
|
617
618
|
const created = await tx
|
|
@@ -657,6 +658,7 @@ class ExecutionPlanService {
|
|
|
657
658
|
nodeSpecs: PlanNodeSpecRecord[],
|
|
658
659
|
): Promise<PlanNodeRunRecord[]> {
|
|
659
660
|
const createdNodeRuns: PlanNodeRunRecord[] = []
|
|
661
|
+
// Sequential: SurrealDB transactions require ordered operations
|
|
660
662
|
for (const nodeSpec of nodeSpecs) {
|
|
661
663
|
const nodeRunId = new RecordId(TABLES.PLAN_NODE_RUN, Bun.randomUUIDv7())
|
|
662
664
|
const created = await tx
|
|
@@ -16,6 +16,7 @@ const embeddings = getDefaultEmbeddings()
|
|
|
16
16
|
const PROMOTION_MIN_USES = 5
|
|
17
17
|
const PROMOTION_MIN_SUCCESS_RATE = 0.6
|
|
18
18
|
|
|
19
|
+
const ACTIVE_SKILL_FILTER = "AND status IN ['learned', 'verified', 'promoted'] AND archivedAt IS NONE"
|
|
19
20
|
const SKILL_EXISTS_TTL_SECONDS = 120
|
|
20
21
|
const SKILL_EXISTS_KEY_PREFIX = 'skill-exists'
|
|
21
22
|
|
|
@@ -176,8 +177,7 @@ class LearnedSkillService {
|
|
|
176
177
|
new BoundQuery(
|
|
177
178
|
`SELECT id FROM ${TABLES.LEARNED_SKILL}
|
|
178
179
|
WHERE organizationId = $orgRef
|
|
179
|
-
|
|
180
|
-
AND archivedAt IS NONE
|
|
180
|
+
${ACTIVE_SKILL_FILTER}
|
|
181
181
|
AND (agentId IS NONE OR agentId = $agentId)
|
|
182
182
|
LIMIT 1`,
|
|
183
183
|
{ orgRef, agentId },
|
|
@@ -227,8 +227,7 @@ class LearnedSkillService {
|
|
|
227
227
|
vector::similarity::cosine(embedding, $embedding) AS similarity
|
|
228
228
|
FROM ${TABLES.LEARNED_SKILL}
|
|
229
229
|
WHERE organizationId = $organizationId
|
|
230
|
-
|
|
231
|
-
AND archivedAt IS NONE
|
|
230
|
+
${ACTIVE_SKILL_FILTER}
|
|
232
231
|
AND confidence >= $minConfidence
|
|
233
232
|
AND (agentId IS NONE OR agentId = $agentId)
|
|
234
233
|
AND embedding <|${params.limit}|> $embedding
|
|
@@ -311,8 +310,7 @@ class LearnedSkillService {
|
|
|
311
310
|
vector::similarity::cosine(embedding, $embedding) AS similarity
|
|
312
311
|
FROM ${TABLES.LEARNED_SKILL}
|
|
313
312
|
WHERE organizationId = $organizationId
|
|
314
|
-
|
|
315
|
-
AND archivedAt IS NONE
|
|
313
|
+
${ACTIVE_SKILL_FILTER}
|
|
316
314
|
AND embedding <|3|> $embedding
|
|
317
315
|
ORDER BY similarity DESC
|
|
318
316
|
LIMIT 1
|
|
@@ -333,8 +331,7 @@ class LearnedSkillService {
|
|
|
333
331
|
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
334
332
|
FROM ${TABLES.LEARNED_SKILL}
|
|
335
333
|
WHERE organizationId = $organizationId
|
|
336
|
-
|
|
337
|
-
AND archivedAt IS NONE
|
|
334
|
+
${ACTIVE_SKILL_FILTER}
|
|
338
335
|
ORDER BY createdAt DESC`,
|
|
339
336
|
{ organizationId: orgRef },
|
|
340
337
|
),
|
|
@@ -357,8 +354,7 @@ class LearnedSkillService {
|
|
|
357
354
|
FROM ${TABLES.LEARNED_SKILL}
|
|
358
355
|
WHERE organizationId = $organizationId
|
|
359
356
|
AND hash = $hash
|
|
360
|
-
|
|
361
|
-
AND archivedAt IS NONE
|
|
357
|
+
${ACTIVE_SKILL_FILTER}
|
|
362
358
|
LIMIT 1`,
|
|
363
359
|
{ organizationId: orgRef, hash },
|
|
364
360
|
),
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { MEMORY } from '../config/constants'
|
|
2
2
|
import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../config/search'
|
|
3
3
|
import type { MemorySearchResult } from '../db/memory-types'
|
|
4
|
-
import {
|
|
4
|
+
import { compactWhitespace } from '../utils/string'
|
|
5
5
|
import type { MemoryRerankOutput } from './memory.service'
|
|
6
6
|
|
|
7
7
|
export function getCandidateLimit(limit: number): number {
|
|
8
|
-
return
|
|
9
|
-
limit,
|
|
10
|
-
multiplier: VECTOR_SEARCH_OVERFETCH_MULTIPLIER,
|
|
11
|
-
minimum: MEMORY.DEFAULT_CANDIDATE_LIMIT,
|
|
12
|
-
})
|
|
8
|
+
return Math.max(limit * VECTOR_SEARCH_OVERFETCH_MULTIPLIER, MEMORY.DEFAULT_CANDIDATE_LIMIT)
|
|
13
9
|
}
|
|
14
10
|
|
|
15
11
|
export function formatMemoryResults(results: MemorySearchResult[]): string {
|
|
16
12
|
if (results.length === 0) return 'No stored memories.'
|
|
17
13
|
|
|
18
14
|
const normalize = (value: string) => {
|
|
19
|
-
const trimmed = value
|
|
15
|
+
const trimmed = compactWhitespace(value)
|
|
20
16
|
if (trimmed.length <= 400) return trimmed
|
|
21
17
|
return `${trimmed.slice(0, 400)}...`
|
|
22
18
|
}
|
|
@@ -65,7 +61,7 @@ export function formatRerankedResults(
|
|
|
65
61
|
used.add(candidate.id)
|
|
66
62
|
total += 1
|
|
67
63
|
const reason = item.relevance ? ` — ${item.relevance}` : ''
|
|
68
|
-
const trimmed = candidate.content
|
|
64
|
+
const trimmed = compactWhitespace(candidate.content)
|
|
69
65
|
const content = trimmed.length <= 400 ? trimmed : `${trimmed.slice(0, 400)}...`
|
|
70
66
|
lines.push(`- ${content}${reason}`)
|
|
71
67
|
if (total >= limit) break
|