@lota-sdk/core 0.1.15 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +12 -8
- package/src/ai/definitions.ts +81 -3
- package/src/ai/embedding-cache.ts +2 -4
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/bifrost/cache-headers.ts +8 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +31 -21
- 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/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +269 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.helpers.ts +1 -3
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/schema-fingerprint.ts +1 -3
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- 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/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +2 -4
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +20 -55
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -2
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +109 -35
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +24 -64
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +3 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +111 -14
- package/src/runtime/runtime-extensions.ts +2 -3
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/social-chat.ts +752 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -32
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +292 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- 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 +2 -4
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +27 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +49 -61
- 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 +11 -4
- 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-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 +384 -40
- 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 +84 -2
- 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.service.ts +28 -34
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/social-chat-history.service.ts +197 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +13 -37
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -89
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/context-compaction.agent.ts +2 -0
- package/src/system-agents/delegated-agent-factory.ts +5 -0
- package/src/system-agents/memory-reranker.agent.ts +4 -2
- package/src/system-agents/memory.agent.ts +2 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
- package/src/system-agents/skill-extractor.agent.ts +2 -0
- package/src/system-agents/skill-manager.agent.ts +2 -0
- package/src/system-agents/title-generator.agent.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/research-topic.tool.ts +2 -0
- package/src/tools/team-think.tool.ts +5 -6
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
- package/src/workers/skill-extraction.runner.ts +26 -6
- package/src/workers/utils/file-section-chunker.ts +2 -1
- package/src/workers/utils/repo-structure-extractor.ts +2 -2
- 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 +14 -25
- package/src/workers/worker-utils.ts +2 -2
- package/src/runtime/workstream-routing-policy.ts +0 -267
- package/src/tools/log-hello-world.tool.ts +0 -17
|
@@ -26,8 +26,7 @@ import {
|
|
|
26
26
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
27
27
|
import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../system-agents/memory-reranker.agent'
|
|
28
28
|
import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../system-agents/memory.agent'
|
|
29
|
-
import {
|
|
30
|
-
import { compactWhitespace } from '../utils/string'
|
|
29
|
+
import { compactWhitespace, truncateText } from '../utils/string'
|
|
31
30
|
import { assessMemoryImportance, clampMemoryImportance } from './memory-assessment.service'
|
|
32
31
|
import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
|
|
33
32
|
|
|
@@ -44,6 +43,7 @@ const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
|
|
|
44
43
|
const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
|
|
45
44
|
const MAX_CONVERSATION_ASSESSMENT_CHARS = 7_000
|
|
46
45
|
const ONBOARDING_MEMORY_MAX_FACTS = 16
|
|
46
|
+
const MAX_ORG_MEMORY_CLIENTS = 128
|
|
47
47
|
const ONBOARDING_MEMORY_EXTRACTION_PROMPT =
|
|
48
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.'
|
|
49
49
|
|
|
@@ -65,17 +65,29 @@ const MemoryRerankOutputSchema = z.object({
|
|
|
65
65
|
|
|
66
66
|
export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
|
|
67
67
|
|
|
68
|
-
const isRoutableAgentName = (value?: string):
|
|
68
|
+
const isRoutableAgentName = (value?: string): value is string => Boolean(value && agentRoster.includes(value))
|
|
69
69
|
|
|
70
70
|
class MemoryService {
|
|
71
71
|
private orgMemoryCache = new Map<string, Memory>()
|
|
72
72
|
|
|
73
73
|
private getOrCreateMemory(cacheKey: string, cache: Map<string, Memory>): Memory {
|
|
74
74
|
const cached = cache.get(cacheKey)
|
|
75
|
-
if (cached)
|
|
75
|
+
if (cached) {
|
|
76
|
+
cache.delete(cacheKey)
|
|
77
|
+
cache.set(cacheKey, cached)
|
|
78
|
+
return cached
|
|
79
|
+
}
|
|
76
80
|
|
|
77
81
|
const memory = new Memory({ createAgent: createOrgMemoryAgent }, { customPrompt: ORG_MEMORY_PROMPT })
|
|
78
82
|
|
|
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
|
+
}
|
|
90
|
+
|
|
79
91
|
cache.set(cacheKey, memory)
|
|
80
92
|
aiLogger.debug`Memory client created and cached for ${cacheKey}`
|
|
81
93
|
return memory
|
|
@@ -87,9 +99,7 @@ class MemoryService {
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
private truncateCandidateText(value: string): string {
|
|
90
|
-
|
|
91
|
-
if (normalized.length <= RERANK_CANDIDATE_MAX_CHARS) return normalized
|
|
92
|
-
return `${normalized.slice(0, RERANK_CANDIDATE_MAX_CHARS)}...`
|
|
102
|
+
return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
private readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
|
|
@@ -129,8 +139,7 @@ class MemoryService {
|
|
|
129
139
|
private normalizeConversationText(value: string, maxChars: number): string {
|
|
130
140
|
const normalized = compactWhitespace(value)
|
|
131
141
|
if (!normalized) return ''
|
|
132
|
-
|
|
133
|
-
return `${normalized.slice(0, maxChars - 3)}...`
|
|
142
|
+
return truncateText(normalized, maxChars)
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
private resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
|
|
@@ -148,9 +157,7 @@ class MemoryService {
|
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
private normalizePreSeededMemoryText(value: string): string {
|
|
151
|
-
|
|
152
|
-
if (normalized.length <= PRESEEDED_MEMORY_MAX_CHARS) return normalized
|
|
153
|
-
return `${normalized.slice(0, PRESEEDED_MEMORY_MAX_CHARS - 3)}...`
|
|
160
|
+
return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
private formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
|
|
@@ -331,15 +338,9 @@ class MemoryService {
|
|
|
331
338
|
aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
|
|
332
339
|
const memory = this.getOrgMemory(orgId)
|
|
333
340
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return results
|
|
338
|
-
} catch (error: unknown) {
|
|
339
|
-
const normalizedError = toError(error)
|
|
340
|
-
aiLogger.error`Organization memory search failed: ${normalizedError}`
|
|
341
|
-
throw normalizedError
|
|
342
|
-
}
|
|
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
|
|
343
344
|
}
|
|
344
345
|
|
|
345
346
|
async getStaleMemories(orgId: string): Promise<string> {
|
|
@@ -368,21 +369,15 @@ class MemoryService {
|
|
|
368
369
|
const searchK = getRuntimeConfig().memory.searchK
|
|
369
370
|
const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
|
|
370
371
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return formatMemoryResults(candidates)
|
|
381
|
-
} catch (error: unknown) {
|
|
382
|
-
const normalizedError = toError(error)
|
|
383
|
-
aiLogger.error`Organization memory search (raw) failed: ${normalizedError}`
|
|
384
|
-
throw normalizedError
|
|
385
|
-
}
|
|
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)
|
|
386
381
|
}
|
|
387
382
|
|
|
388
383
|
async searchAgentMemories(orgId: string, agentName: string, query: string): Promise<string> {
|
|
@@ -395,20 +390,14 @@ class MemoryService {
|
|
|
395
390
|
aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
|
|
396
391
|
const memory = this.getOrgMemory(orgId)
|
|
397
392
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
return results
|
|
402
|
-
} catch (error: unknown) {
|
|
403
|
-
const normalizedError = toError(error)
|
|
404
|
-
aiLogger.error`Agent memory search failed: ${normalizedError}`
|
|
405
|
-
throw normalizedError
|
|
406
|
-
}
|
|
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
|
|
407
396
|
}
|
|
408
397
|
|
|
409
398
|
async searchOrgMemoriesForAgent(orgId: string, agentName: string, query: string): Promise<string> {
|
|
410
399
|
if (!isRoutableAgentName(agentName)) {
|
|
411
|
-
return
|
|
400
|
+
return this.searchOrganizationMemories(orgId, query)
|
|
412
401
|
}
|
|
413
402
|
|
|
414
403
|
const [agentResult, orgResult] = await Promise.all([
|
|
@@ -429,7 +418,7 @@ class MemoryService {
|
|
|
429
418
|
}): Promise<MemoryRecord[]> {
|
|
430
419
|
const { orgId, ...listOptions } = params
|
|
431
420
|
const orgMemory = this.getOrgMemory(orgId)
|
|
432
|
-
return
|
|
421
|
+
return orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
|
|
433
422
|
}
|
|
434
423
|
|
|
435
424
|
async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
|
|
@@ -513,7 +502,7 @@ class MemoryService {
|
|
|
513
502
|
]
|
|
514
503
|
|
|
515
504
|
if (isRoutableAgentName(agentName)) {
|
|
516
|
-
const agentScoped = agentScopeId(orgId, agentName
|
|
505
|
+
const agentScoped = agentScopeId(orgId, agentName)
|
|
517
506
|
retrievalTasks.push({
|
|
518
507
|
scopeTag: `agent:${agentName}`,
|
|
519
508
|
retrieve: async () =>
|
|
@@ -649,7 +638,7 @@ class MemoryService {
|
|
|
649
638
|
importance?: number
|
|
650
639
|
}): Promise<string> {
|
|
651
640
|
if (!isRoutableAgentName(agentName)) {
|
|
652
|
-
throw new Error(`Invalid agentName for agent memory: ${agentName}`)
|
|
641
|
+
throw new Error(`Invalid agentName for agent memory: ${agentName as string}`)
|
|
653
642
|
}
|
|
654
643
|
|
|
655
644
|
const memory = this.getOrgMemory(orgId)
|
|
@@ -733,6 +722,8 @@ class MemoryService {
|
|
|
733
722
|
input,
|
|
734
723
|
output,
|
|
735
724
|
sourceId,
|
|
725
|
+
source = 'chat',
|
|
726
|
+
sourceMetadata,
|
|
736
727
|
onboardStatus,
|
|
737
728
|
agentName,
|
|
738
729
|
historyMessages = [],
|
|
@@ -744,6 +735,8 @@ class MemoryService {
|
|
|
744
735
|
input: string
|
|
745
736
|
output: string
|
|
746
737
|
sourceId?: string
|
|
738
|
+
source?: string
|
|
739
|
+
sourceMetadata?: Record<string, unknown>
|
|
747
740
|
onboardStatus?: string
|
|
748
741
|
agentName?: string
|
|
749
742
|
historyMessages?: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
|
|
@@ -800,7 +793,7 @@ class MemoryService {
|
|
|
800
793
|
scopeId: orgScopeId,
|
|
801
794
|
memoryType: ORG_MEMORY_TYPE,
|
|
802
795
|
...(typeof assessedImportance === 'number' ? { importance: assessedImportance } : {}),
|
|
803
|
-
metadata: { orgId, source
|
|
796
|
+
metadata: { orgId, source, ...(sourceId ? { sourceId } : {}), ...sourceMetadata, ...assessmentMetadata },
|
|
804
797
|
},
|
|
805
798
|
]
|
|
806
799
|
|
|
@@ -814,8 +807,9 @@ class MemoryService {
|
|
|
814
807
|
orgId,
|
|
815
808
|
agentName: scopedAgentName,
|
|
816
809
|
memoryScope: 'agent',
|
|
817
|
-
source
|
|
810
|
+
source,
|
|
818
811
|
...(sourceId ? { sourceId } : {}),
|
|
812
|
+
...sourceMetadata,
|
|
819
813
|
...assessmentMetadata,
|
|
820
814
|
},
|
|
821
815
|
})
|
|
@@ -830,16 +824,10 @@ class MemoryService {
|
|
|
830
824
|
const preparedUpdates = await orgMemory.prepareFactsToScopes(extractedFacts, scopes)
|
|
831
825
|
if (preparedUpdates.length === 0) return
|
|
832
826
|
|
|
833
|
-
|
|
834
|
-
await
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
|
|
838
|
-
} catch (error: unknown) {
|
|
839
|
-
const normalizedError = toError(error)
|
|
840
|
-
aiLogger.error`Memory write failed: ${normalizedError}`
|
|
841
|
-
throw normalizedError
|
|
842
|
-
}
|
|
827
|
+
await withOrgMemoryLock(orgId, async () => {
|
|
828
|
+
await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
|
|
829
|
+
})
|
|
830
|
+
aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
|
|
843
831
|
}
|
|
844
832
|
}
|
|
845
833
|
|
|
@@ -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
|
})
|
|
@@ -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> {
|