@lota-sdk/core 0.1.14 → 0.1.16
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/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +12 -5
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +386 -58
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +87 -20
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- 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 +5 -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 +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- 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 +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- 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 +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- 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
|
@@ -24,10 +24,11 @@ import {
|
|
|
24
24
|
scopedRetrievalToMap,
|
|
25
25
|
} from '../runtime/retrieval-adapters'
|
|
26
26
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
27
|
-
import { createMemoryRerankerAgent,
|
|
28
|
-
import { createOrgMemoryAgent,
|
|
27
|
+
import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../system-agents/memory-reranker.agent'
|
|
28
|
+
import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../system-agents/memory.agent'
|
|
29
|
+
import { compactWhitespace, truncateText } from '../utils/string'
|
|
29
30
|
import { assessMemoryImportance, clampMemoryImportance } from './memory-assessment.service'
|
|
30
|
-
import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory
|
|
31
|
+
import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
|
|
31
32
|
|
|
32
33
|
const ORG_MEMORY_TYPE = 'fact'
|
|
33
34
|
const RERANK_CANDIDATE_MAX_CHARS = 500
|
|
@@ -42,6 +43,7 @@ const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
|
|
|
42
43
|
const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
|
|
43
44
|
const MAX_CONVERSATION_ASSESSMENT_CHARS = 7_000
|
|
44
45
|
const ONBOARDING_MEMORY_MAX_FACTS = 16
|
|
46
|
+
const MAX_ORG_MEMORY_CLIENTS = 128
|
|
45
47
|
const ONBOARDING_MEMORY_EXTRACTION_PROMPT =
|
|
46
48
|
'Onboarding mode is active. Extract multiple concrete startup facts from user-provided context: company mission, product capabilities, customer segments, pricing, traction, go-to-market plans, roadmap, team composition, technical stack, risks, and referenced URLs. Prefer one fact per concrete claim.'
|
|
47
49
|
|
|
@@ -63,16 +65,28 @@ const MemoryRerankOutputSchema = z.object({
|
|
|
63
65
|
|
|
64
66
|
export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
|
|
65
67
|
|
|
66
|
-
const isRoutableAgentName = (value?: string):
|
|
68
|
+
const isRoutableAgentName = (value?: string): value is string => Boolean(value && agentRoster.includes(value))
|
|
67
69
|
|
|
68
70
|
class MemoryService {
|
|
69
71
|
private orgMemoryCache = new Map<string, Memory>()
|
|
70
72
|
|
|
71
73
|
private getOrCreateMemory(cacheKey: string, cache: Map<string, Memory>): Memory {
|
|
72
74
|
const cached = cache.get(cacheKey)
|
|
73
|
-
if (cached)
|
|
75
|
+
if (cached) {
|
|
76
|
+
cache.delete(cacheKey)
|
|
77
|
+
cache.set(cacheKey, cached)
|
|
78
|
+
return cached
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const memory = new Memory({ createAgent: createOrgMemoryAgent }, { customPrompt: ORG_MEMORY_PROMPT })
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
if (cache.size >= MAX_ORG_MEMORY_CLIENTS) {
|
|
84
|
+
const oldestKey = cache.keys().next().value
|
|
85
|
+
if (typeof oldestKey === 'string') {
|
|
86
|
+
cache.delete(oldestKey)
|
|
87
|
+
aiLogger.debug`Evicted cached org memory client for ${oldestKey}`
|
|
88
|
+
}
|
|
89
|
+
}
|
|
76
90
|
|
|
77
91
|
cache.set(cacheKey, memory)
|
|
78
92
|
aiLogger.debug`Memory client created and cached for ${cacheKey}`
|
|
@@ -85,9 +99,7 @@ class MemoryService {
|
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
private truncateCandidateText(value: string): string {
|
|
88
|
-
|
|
89
|
-
if (normalized.length <= RERANK_CANDIDATE_MAX_CHARS) return normalized
|
|
90
|
-
return `${normalized.slice(0, RERANK_CANDIDATE_MAX_CHARS)}...`
|
|
102
|
+
return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
private readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
|
|
@@ -125,10 +137,9 @@ class MemoryService {
|
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
private normalizeConversationText(value: string, maxChars: number): string {
|
|
128
|
-
const normalized = value
|
|
140
|
+
const normalized = compactWhitespace(value)
|
|
129
141
|
if (!normalized) return ''
|
|
130
|
-
|
|
131
|
-
return `${normalized.slice(0, maxChars - 3)}...`
|
|
142
|
+
return truncateText(normalized, maxChars)
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
private resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
|
|
@@ -146,9 +157,7 @@ class MemoryService {
|
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
private normalizePreSeededMemoryText(value: string): string {
|
|
149
|
-
|
|
150
|
-
if (normalized.length <= PRESEEDED_MEMORY_MAX_CHARS) return normalized
|
|
151
|
-
return `${normalized.slice(0, PRESEEDED_MEMORY_MAX_CHARS - 3)}...`
|
|
160
|
+
return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
private formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
|
|
@@ -232,7 +241,7 @@ class MemoryService {
|
|
|
232
241
|
return await helperModelRuntime.generateHelperStructured({
|
|
233
242
|
tag: 'memory-reranker',
|
|
234
243
|
createAgent: createMemoryRerankerAgent,
|
|
235
|
-
defaultSystemPrompt:
|
|
244
|
+
defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
|
|
236
245
|
messages: [
|
|
237
246
|
{
|
|
238
247
|
role: 'user',
|
|
@@ -269,7 +278,7 @@ class MemoryService {
|
|
|
269
278
|
return await helperModelRuntime.generateHelperStructured({
|
|
270
279
|
tag: 'memory-reranker-multi-scope',
|
|
271
280
|
createAgent: createMemoryRerankerAgent,
|
|
272
|
-
defaultSystemPrompt:
|
|
281
|
+
defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
|
|
273
282
|
messages: [
|
|
274
283
|
{
|
|
275
284
|
role: 'user',
|
|
@@ -329,15 +338,9 @@ class MemoryService {
|
|
|
329
338
|
aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
|
|
330
339
|
const memory = this.getOrgMemory(orgId)
|
|
331
340
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return results
|
|
336
|
-
} catch (error: unknown) {
|
|
337
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
338
|
-
aiLogger.error`Organization memory search failed: ${normalizedError}`
|
|
339
|
-
throw normalizedError
|
|
340
|
-
}
|
|
341
|
+
const results = await this.searchMemories({ query, memory, scopeId: orgScopeId, memoryType: ORG_MEMORY_TYPE })
|
|
342
|
+
aiLogger.debug`Organization memory search completed (resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
|
|
343
|
+
return results
|
|
341
344
|
}
|
|
342
345
|
|
|
343
346
|
async getStaleMemories(orgId: string): Promise<string> {
|
|
@@ -348,7 +351,8 @@ class MemoryService {
|
|
|
348
351
|
if (stale.length === 0) return ''
|
|
349
352
|
const items = stale.map((m) => `- [NEEDS REVIEW] ${m.content}`).join('\n')
|
|
350
353
|
return `Memories flagged for review (parent fact was superseded):\n${items}`
|
|
351
|
-
} catch {
|
|
354
|
+
} catch (error) {
|
|
355
|
+
aiLogger.warn`Failed to get stale memories: ${error}`
|
|
352
356
|
return ''
|
|
353
357
|
}
|
|
354
358
|
}
|
|
@@ -359,27 +363,21 @@ class MemoryService {
|
|
|
359
363
|
options?: { fastMode?: boolean; limit?: number },
|
|
360
364
|
): Promise<string> {
|
|
361
365
|
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
362
|
-
aiLogger.
|
|
366
|
+
aiLogger.debug`searchOrganizationMemoriesRaw - orgId: "${orgId}", scopeId: "${orgScopeId}"`
|
|
363
367
|
const memory = this.getOrgMemory(orgId)
|
|
364
368
|
const fastMode = options?.fastMode ?? true
|
|
365
369
|
const searchK = getRuntimeConfig().memory.searchK
|
|
366
370
|
const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
|
|
367
371
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return formatMemoryResults(candidates)
|
|
378
|
-
} catch (error: unknown) {
|
|
379
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
380
|
-
aiLogger.error`Organization memory search (raw) failed: ${normalizedError}`
|
|
381
|
-
throw normalizedError
|
|
382
|
-
}
|
|
372
|
+
const candidates = await memory.searchCandidates(query, {
|
|
373
|
+
scopeId: orgScopeId,
|
|
374
|
+
limit,
|
|
375
|
+
memoryType: ORG_MEMORY_TYPE,
|
|
376
|
+
fastMode,
|
|
377
|
+
includeNeighborContext: !fastMode,
|
|
378
|
+
})
|
|
379
|
+
aiLogger.debug`Organization memory search (raw) completed (candidates: ${candidates.length})`
|
|
380
|
+
return formatMemoryResults(candidates)
|
|
383
381
|
}
|
|
384
382
|
|
|
385
383
|
async searchAgentMemories(orgId: string, agentName: string, query: string): Promise<string> {
|
|
@@ -392,20 +390,14 @@ class MemoryService {
|
|
|
392
390
|
aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
|
|
393
391
|
const memory = this.getOrgMemory(orgId)
|
|
394
392
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return results
|
|
399
|
-
} catch (error: unknown) {
|
|
400
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
401
|
-
aiLogger.error`Agent memory search failed: ${normalizedError}`
|
|
402
|
-
throw normalizedError
|
|
403
|
-
}
|
|
393
|
+
const results = await this.searchMemories({ query, memory, scopeId: scoped, memoryType: ORG_MEMORY_TYPE })
|
|
394
|
+
aiLogger.debug`Agent memory search completed (agentName: ${agentName}, resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
|
|
395
|
+
return results
|
|
404
396
|
}
|
|
405
397
|
|
|
406
398
|
async searchOrgMemoriesForAgent(orgId: string, agentName: string, query: string): Promise<string> {
|
|
407
399
|
if (!isRoutableAgentName(agentName)) {
|
|
408
|
-
return
|
|
400
|
+
return this.searchOrganizationMemories(orgId, query)
|
|
409
401
|
}
|
|
410
402
|
|
|
411
403
|
const [agentResult, orgResult] = await Promise.all([
|
|
@@ -426,7 +418,7 @@ class MemoryService {
|
|
|
426
418
|
}): Promise<MemoryRecord[]> {
|
|
427
419
|
const { orgId, ...listOptions } = params
|
|
428
420
|
const orgMemory = this.getOrgMemory(orgId)
|
|
429
|
-
return
|
|
421
|
+
return orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
|
|
430
422
|
}
|
|
431
423
|
|
|
432
424
|
async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
|
|
@@ -467,7 +459,7 @@ class MemoryService {
|
|
|
467
459
|
const deduped: MemoryRecord[] = []
|
|
468
460
|
const seen = new Set<string>()
|
|
469
461
|
for (const memory of combined) {
|
|
470
|
-
const normalizedKey = memory.content
|
|
462
|
+
const normalizedKey = compactWhitespace(memory.content).toLowerCase()
|
|
471
463
|
if (!normalizedKey || seen.has(normalizedKey)) continue
|
|
472
464
|
seen.add(normalizedKey)
|
|
473
465
|
deduped.push(memory)
|
|
@@ -510,7 +502,7 @@ class MemoryService {
|
|
|
510
502
|
]
|
|
511
503
|
|
|
512
504
|
if (isRoutableAgentName(agentName)) {
|
|
513
|
-
const agentScoped = agentScopeId(orgId, agentName
|
|
505
|
+
const agentScoped = agentScopeId(orgId, agentName)
|
|
514
506
|
retrievalTasks.push({
|
|
515
507
|
scopeTag: `agent:${agentName}`,
|
|
516
508
|
retrieve: async () =>
|
|
@@ -594,7 +586,7 @@ class MemoryService {
|
|
|
594
586
|
durability?: MemoryRecord['durability']
|
|
595
587
|
}): Promise<string> {
|
|
596
588
|
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
597
|
-
aiLogger.
|
|
589
|
+
aiLogger.debug`createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
|
|
598
590
|
const memory = this.getOrgMemory(orgId)
|
|
599
591
|
try {
|
|
600
592
|
return await memory.insert(content, {
|
|
@@ -646,7 +638,7 @@ class MemoryService {
|
|
|
646
638
|
importance?: number
|
|
647
639
|
}): Promise<string> {
|
|
648
640
|
if (!isRoutableAgentName(agentName)) {
|
|
649
|
-
throw new Error(`Invalid agentName for agent memory: ${agentName}`)
|
|
641
|
+
throw new Error(`Invalid agentName for agent memory: ${agentName as string}`)
|
|
650
642
|
}
|
|
651
643
|
|
|
652
644
|
const memory = this.getOrgMemory(orgId)
|
|
@@ -762,7 +754,7 @@ class MemoryService {
|
|
|
762
754
|
}
|
|
763
755
|
|
|
764
756
|
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
765
|
-
aiLogger.
|
|
757
|
+
aiLogger.debug`addConversationMemories - orgId: "${orgId}", scopeId: "${orgScopeId}", sourceId: ${sourceId ?? 'none'}`
|
|
766
758
|
|
|
767
759
|
const orgMemory = this.getOrgMemory(orgId)
|
|
768
760
|
let assessedImportance: number | undefined
|
|
@@ -827,16 +819,10 @@ class MemoryService {
|
|
|
827
819
|
const preparedUpdates = await orgMemory.prepareFactsToScopes(extractedFacts, scopes)
|
|
828
820
|
if (preparedUpdates.length === 0) return
|
|
829
821
|
|
|
830
|
-
|
|
831
|
-
await
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
|
|
835
|
-
} catch (error: unknown) {
|
|
836
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
837
|
-
aiLogger.error`Memory write failed: ${normalizedError}`
|
|
838
|
-
throw normalizedError
|
|
839
|
-
}
|
|
822
|
+
await withOrgMemoryLock(orgId, async () => {
|
|
823
|
+
await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
|
|
824
|
+
})
|
|
825
|
+
aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
|
|
840
826
|
}
|
|
841
827
|
}
|
|
842
828
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { MonitoringWindowConfig } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { planSchedulerService } from './plan-scheduler.service'
|
|
4
|
+
|
|
5
|
+
class MonitoringWindowService {
|
|
6
|
+
computeCheckTimes(config: MonitoringWindowConfig): { offsetMs: number; intervalMs: number }[] {
|
|
7
|
+
const times: { offsetMs: number; intervalMs: number }[] = []
|
|
8
|
+
let currentInterval = config.initialIntervalMs
|
|
9
|
+
let elapsed = 0
|
|
10
|
+
while (elapsed < config.durationMs) {
|
|
11
|
+
times.push({ offsetMs: elapsed, intervalMs: currentInterval })
|
|
12
|
+
elapsed += currentInterval
|
|
13
|
+
currentInterval = Math.min(currentInterval / config.decayFactor, config.maxIntervalMs)
|
|
14
|
+
}
|
|
15
|
+
return times
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async startMonitoringWindow(params: {
|
|
19
|
+
runId: string
|
|
20
|
+
nodeId: string
|
|
21
|
+
config: MonitoringWindowConfig
|
|
22
|
+
organizationId: string
|
|
23
|
+
workstreamId: string
|
|
24
|
+
}): Promise<void> {
|
|
25
|
+
await planSchedulerService.createSchedule({
|
|
26
|
+
organizationId: params.organizationId,
|
|
27
|
+
workstreamId: params.workstreamId,
|
|
28
|
+
runId: params.runId,
|
|
29
|
+
nodeId: params.nodeId,
|
|
30
|
+
scheduleSpec: {
|
|
31
|
+
type: 'monitoring',
|
|
32
|
+
intervalMs: params.config.initialIntervalMs,
|
|
33
|
+
maxFires: this.computeCheckTimes(params.config).length,
|
|
34
|
+
missedPolicy: 'skip',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
evaluateTermination(params: { samples: unknown[]; config: MonitoringWindowConfig }): boolean {
|
|
40
|
+
if (!params.config.earlyTermination) return false
|
|
41
|
+
if (params.samples.length < params.config.earlyTermination.minSamples) return false
|
|
42
|
+
|
|
43
|
+
const { condition, minSamples } = params.config.earlyTermination
|
|
44
|
+
const numericSamples = params.samples
|
|
45
|
+
.map((s) =>
|
|
46
|
+
typeof s === 'number'
|
|
47
|
+
? s
|
|
48
|
+
: typeof s === 'object' && s !== null && 'value' in s
|
|
49
|
+
? Number((s as Record<string, unknown>).value)
|
|
50
|
+
: NaN,
|
|
51
|
+
)
|
|
52
|
+
.filter((n) => !isNaN(n))
|
|
53
|
+
|
|
54
|
+
// No numeric data to evaluate against — don't terminate
|
|
55
|
+
if (numericSamples.length === 0) return false
|
|
56
|
+
|
|
57
|
+
if (condition === 'stable') {
|
|
58
|
+
const recent = numericSamples.slice(-minSamples)
|
|
59
|
+
if (recent.length < 2) return false
|
|
60
|
+
const mean = recent.reduce((a, b) => a + b, 0) / recent.length
|
|
61
|
+
const variance = recent.reduce((a, b) => a + (b - mean) ** 2, 0) / recent.length
|
|
62
|
+
const cv = mean !== 0 ? Math.sqrt(variance) / Math.abs(mean) : 0
|
|
63
|
+
return cv < 0.1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const thresholdMatch = condition.match(/^threshold:(\d+(?:\.\d+)?)$/)
|
|
67
|
+
if (thresholdMatch?.[1]) {
|
|
68
|
+
const threshold = parseFloat(thresholdMatch[1])
|
|
69
|
+
return numericSamples.some((v) => v >= threshold)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (condition === 'trend:declining') {
|
|
73
|
+
const recent = numericSamples.slice(-minSamples)
|
|
74
|
+
return recent.length >= 2 && recent.every((v, i) => i === 0 || v <= (recent[i - 1] ?? v))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (condition === 'trend:increasing') {
|
|
78
|
+
const recent = numericSamples.slice(-minSamples)
|
|
79
|
+
return recent.length >= 2 && recent.every((v, i) => i === 0 || v >= (recent[i - 1] ?? v))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw new Error(`Unknown monitoring termination condition: ${condition}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const monitoringWindowService = new MonitoringWindowService()
|
|
@@ -106,5 +106,5 @@ async function verifyMutatingApprovalForWorkstream(
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
export const verifyMutatingApproval: VerifyMutatingApproval = async (params) => {
|
|
109
|
-
return
|
|
109
|
+
return verifyMutatingApprovalForWorkstream(ensureRecordId(params.workstreamId, TABLES.WORKSTREAM), params)
|
|
110
110
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NodeResultQuality,
|
|
3
|
+
PlanArtifactSubmission,
|
|
4
|
+
PlanNodeResult,
|
|
5
|
+
PlanNodeSpec,
|
|
6
|
+
PlanSchemaRegistry,
|
|
7
|
+
WriteIntent,
|
|
8
|
+
WriteIntentAction,
|
|
9
|
+
} from '@lota-sdk/shared'
|
|
10
|
+
|
|
11
|
+
import { serverLogger } from '../config/logger'
|
|
12
|
+
|
|
13
|
+
export type WorkspaceEntryValidation = 'validated' | 'unvalidated'
|
|
14
|
+
|
|
15
|
+
export interface WorkspaceEntry {
|
|
16
|
+
targetPath: string
|
|
17
|
+
action: WriteIntentAction
|
|
18
|
+
payload: unknown
|
|
19
|
+
validation: WorkspaceEntryValidation
|
|
20
|
+
stagedAt: Date
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NodeWorkspace {
|
|
24
|
+
nodeId: string
|
|
25
|
+
ctx: Readonly<{
|
|
26
|
+
resolvedInput: Record<string, unknown>
|
|
27
|
+
inputArtifacts: PlanArtifactSubmission[]
|
|
28
|
+
schemaRegistry: PlanSchemaRegistry
|
|
29
|
+
nodeSpec: PlanNodeSpec
|
|
30
|
+
}>
|
|
31
|
+
work: Map<string, unknown>
|
|
32
|
+
sys: {
|
|
33
|
+
startedAt: Date
|
|
34
|
+
correctionCounts: Map<string, number>
|
|
35
|
+
writeLog: Array<{ targetPath: string; action: WriteIntentAction; timestamp: Date; result: 'accepted' | 'rejected' }>
|
|
36
|
+
}
|
|
37
|
+
deliverables: Map<string, WorkspaceEntry>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class NodeWorkspaceService {
|
|
41
|
+
initialize(params: {
|
|
42
|
+
nodeSpec: PlanNodeSpec
|
|
43
|
+
resolvedInput: Record<string, unknown>
|
|
44
|
+
inputArtifacts: PlanArtifactSubmission[]
|
|
45
|
+
schemaRegistry: PlanSchemaRegistry
|
|
46
|
+
}): NodeWorkspace {
|
|
47
|
+
const ctx = Object.freeze({
|
|
48
|
+
resolvedInput: params.resolvedInput,
|
|
49
|
+
inputArtifacts: params.inputArtifacts,
|
|
50
|
+
schemaRegistry: params.schemaRegistry,
|
|
51
|
+
nodeSpec: params.nodeSpec,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
nodeId: params.nodeSpec.id,
|
|
56
|
+
ctx,
|
|
57
|
+
work: new Map(),
|
|
58
|
+
sys: { startedAt: new Date(), correctionCounts: new Map(), writeLog: [] },
|
|
59
|
+
deliverables: new Map(),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stageWrite(workspace: NodeWorkspace, intent: WriteIntent, validation: WorkspaceEntryValidation): void {
|
|
64
|
+
const existing = workspace.deliverables.get(intent.targetPath)
|
|
65
|
+
|
|
66
|
+
if (intent.action === 'append' && existing && Array.isArray(existing.payload) && Array.isArray(intent.payload)) {
|
|
67
|
+
existing.payload = [...(existing.payload as unknown[]), ...intent.payload]
|
|
68
|
+
existing.action = intent.action
|
|
69
|
+
existing.validation = validation
|
|
70
|
+
existing.stagedAt = new Date()
|
|
71
|
+
} else {
|
|
72
|
+
workspace.deliverables.set(intent.targetPath, {
|
|
73
|
+
targetPath: intent.targetPath,
|
|
74
|
+
action: intent.action,
|
|
75
|
+
payload: intent.payload,
|
|
76
|
+
validation,
|
|
77
|
+
stagedAt: new Date(),
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
workspace.sys.writeLog.push({
|
|
82
|
+
targetPath: intent.targetPath,
|
|
83
|
+
action: intent.action,
|
|
84
|
+
timestamp: new Date(),
|
|
85
|
+
result: 'accepted',
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
finalize(workspace: NodeWorkspace): PlanNodeResult & { isComplete: boolean; quality: NodeResultQuality } {
|
|
90
|
+
const artifacts: PlanArtifactSubmission[] = []
|
|
91
|
+
let structuredOutput: Record<string, unknown> | undefined
|
|
92
|
+
let allValidated = true
|
|
93
|
+
const requiredNames = new Set(workspace.ctx.nodeSpec.deliverables.filter((d) => d.required).map((d) => d.name))
|
|
94
|
+
const presentRequired = new Set<string>()
|
|
95
|
+
|
|
96
|
+
for (const [targetPath, entry] of workspace.deliverables) {
|
|
97
|
+
if (entry.validation === 'unvalidated') {
|
|
98
|
+
allValidated = false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (targetPath.startsWith('structuredOutput')) {
|
|
102
|
+
if (!structuredOutput) structuredOutput = {}
|
|
103
|
+
const subPath = targetPath.replace(/^structuredOutput\.?/, '')
|
|
104
|
+
if (subPath) {
|
|
105
|
+
structuredOutput[subPath] = entry.payload
|
|
106
|
+
} else {
|
|
107
|
+
structuredOutput =
|
|
108
|
+
typeof entry.payload === 'object' && entry.payload !== null && !Array.isArray(entry.payload)
|
|
109
|
+
? (entry.payload as Record<string, unknown>)
|
|
110
|
+
: structuredOutput
|
|
111
|
+
}
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const spec = workspace.ctx.nodeSpec.deliverables.find((d) => d.name === targetPath)
|
|
116
|
+
if (!spec) {
|
|
117
|
+
serverLogger.warn`Write to unknown deliverable path "${targetPath}" in node ${workspace.nodeId} — skipping`
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (requiredNames.has(targetPath)) {
|
|
122
|
+
presentRequired.add(targetPath)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
artifacts.push({
|
|
126
|
+
name: spec.name,
|
|
127
|
+
kind: spec.kind,
|
|
128
|
+
pointer: `workspace://${workspace.nodeId}/${targetPath}`,
|
|
129
|
+
schemaRef: spec.schemaRef,
|
|
130
|
+
description: spec.description,
|
|
131
|
+
payload:
|
|
132
|
+
typeof entry.payload === 'string'
|
|
133
|
+
? { content: entry.payload }
|
|
134
|
+
: typeof entry.payload === 'object' && entry.payload !== null
|
|
135
|
+
? (entry.payload as Record<string, unknown> | unknown[])
|
|
136
|
+
: undefined,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const allRequiredPresent = requiredNames.size === presentRequired.size
|
|
141
|
+
const quality: NodeResultQuality = allRequiredPresent && allValidated ? 'full' : 'partial'
|
|
142
|
+
const isComplete = quality === 'full'
|
|
143
|
+
|
|
144
|
+
return { structuredOutput, artifacts, quality, isComplete }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
cleanup(workspace: NodeWorkspace): void {
|
|
148
|
+
workspace.work.clear()
|
|
149
|
+
workspace.deliverables.clear()
|
|
150
|
+
workspace.sys.writeLog.length = 0
|
|
151
|
+
workspace.sys.correctionCounts.clear()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const nodeWorkspaceService = new NodeWorkspaceService()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NotificationSeverity } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
export interface NotificationPayload {
|
|
4
|
+
organizationId: string
|
|
5
|
+
workstreamId: string
|
|
6
|
+
runId?: string
|
|
7
|
+
nodeId?: string
|
|
8
|
+
severity: NotificationSeverity
|
|
9
|
+
title: string
|
|
10
|
+
body: string
|
|
11
|
+
dedupeKey?: string
|
|
12
|
+
metadata?: Record<string, unknown>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NotificationService {
|
|
16
|
+
notify(payload: NotificationPayload): Promise<void>
|
|
17
|
+
remind(payload: NotificationPayload): Promise<void>
|
|
18
|
+
escalate(payload: NotificationPayload): Promise<void>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class NoOpNotificationService implements NotificationService {
|
|
22
|
+
async notify(_payload: NotificationPayload): Promise<void> {}
|
|
23
|
+
async remind(_payload: NotificationPayload): Promise<void> {}
|
|
24
|
+
async escalate(_payload: NotificationPayload): Promise<void> {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let _notificationService: NotificationService | null = null
|
|
28
|
+
|
|
29
|
+
export function configureNotificationService(service: NotificationService | null): void {
|
|
30
|
+
_notificationService = service
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getNotificationService(): NotificationService {
|
|
34
|
+
if (_notificationService === null) {
|
|
35
|
+
throw new Error('Notification service is not configured.')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return _notificationService
|
|
39
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { recordIdStringSchema } from '@lota-sdk/shared'
|
|
1
|
+
import { recordIdSchema, recordIdStringSchema } from '@lota-sdk/shared'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import { BaseService } from '../db/base.service'
|
|
@@ -8,9 +8,9 @@ import { TABLES } from '../db/tables'
|
|
|
8
8
|
import { toIsoDateTimeString } from '../utils/date-time'
|
|
9
9
|
|
|
10
10
|
const organizationMemberRecordSchema = z.object({
|
|
11
|
-
id:
|
|
12
|
-
in:
|
|
13
|
-
out:
|
|
11
|
+
id: recordIdSchema,
|
|
12
|
+
in: recordIdSchema,
|
|
13
|
+
out: recordIdSchema,
|
|
14
14
|
role: z.string(),
|
|
15
15
|
createdAt: z.coerce.date(),
|
|
16
16
|
})
|
|
@@ -23,7 +23,7 @@ const sdkOrganizationMemberSchema = z.object({
|
|
|
23
23
|
createdAt: z.iso.datetime(),
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
type SdkOrganizationMemberRecord = z.infer<typeof organizationMemberRecordSchema>
|
|
27
27
|
export type SdkOrganizationMember = z.infer<typeof sdkOrganizationMemberSchema>
|
|
28
28
|
|
|
29
29
|
class OrganizationMemberService extends BaseService<typeof organizationMemberRecordSchema> {
|
|
@@ -91,6 +91,13 @@ class OrganizationMemberService extends BaseService<typeof organizationMemberRec
|
|
|
91
91
|
)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async getMembership(userId: RecordIdInput, organizationId: RecordIdInput): Promise<SdkOrganizationMember | null> {
|
|
95
|
+
const userRef = ensureRecordId(userId, TABLES.USER)
|
|
96
|
+
const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
|
|
97
|
+
const record = (await this.findAll({ in: userRef, out: organizationRef }, { limit: 1 })).at(0)
|
|
98
|
+
return record ? this.toPublic(record) : null
|
|
99
|
+
}
|
|
100
|
+
|
|
94
101
|
async isMember(userId: RecordIdInput, organizationId: RecordIdInput): Promise<boolean> {
|
|
95
102
|
const userRef = ensureRecordId(userId, TABLES.USER)
|
|
96
103
|
const organizationRef = ensureRecordId(organizationId, TABLES.ORGANIZATION)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { recordIdStringSchema } from '@lota-sdk/shared'
|
|
1
|
+
import { recordIdSchema, recordIdStringSchema } from '@lota-sdk/shared'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import { BaseService } from '../db/base.service'
|
|
@@ -9,12 +9,12 @@ import { TABLES } from '../db/tables'
|
|
|
9
9
|
import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
|
|
10
10
|
|
|
11
11
|
const organizationRecordSchema = z.object({
|
|
12
|
-
id:
|
|
12
|
+
id: recordIdSchema,
|
|
13
13
|
name: z.string(),
|
|
14
|
-
regularChatDigestLastWorkstreamCursorCreatedAt: z.
|
|
14
|
+
regularChatDigestLastWorkstreamCursorCreatedAt: z.coerce.date().optional(),
|
|
15
15
|
regularChatDigestLastWorkstreamCursorId: z.string().optional(),
|
|
16
16
|
skillExtractionLastCursorId: z.string().optional(),
|
|
17
|
-
skillExtractionLastCursorCreatedAt: z.
|
|
17
|
+
skillExtractionLastCursorCreatedAt: z.coerce.date().optional(),
|
|
18
18
|
createdAt: z.coerce.date(),
|
|
19
19
|
updatedAt: z.coerce.date(),
|
|
20
20
|
})
|
|
@@ -86,7 +86,7 @@ class OrganizationService extends BaseService<typeof organizationRecordSchema> {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async getOrganizationRecord(organizationId: RecordIdRef): Promise<SdkOrganizationRecord> {
|
|
89
|
-
return
|
|
89
|
+
return this.getById(ensureRecordId(organizationId, TABLES.ORGANIZATION))
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async updateOrganization(organizationId: RecordIdInput, params: { name: string }): Promise<SdkOrganization> {
|