@lota-sdk/core 0.4.12 → 0.4.14
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 +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/live-turn-trace.ts +6 -49
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
|
@@ -2,7 +2,7 @@ import type { ChatMessage } from '@lota-sdk/shared'
|
|
|
2
2
|
import { Duration, Effect, Schedule, Schema } from 'effect'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
6
6
|
import { nowEpochMillis } from '../../utils/date-time'
|
|
7
7
|
import {
|
|
8
8
|
CHARS_PER_TOKEN_ESTIMATE,
|
|
@@ -60,12 +60,12 @@ export interface ContextCompactionRunnerParams {
|
|
|
60
60
|
transcript: string
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
class CompactionError extends Schema.TaggedErrorClass<CompactionError>()(
|
|
63
|
+
class CompactionError extends Schema.TaggedErrorClass<CompactionError>()(ERROR_TAGS.CompactionError, {
|
|
64
64
|
message: Schema.String,
|
|
65
65
|
cause: Schema.optional(Schema.Defect),
|
|
66
66
|
}) {}
|
|
67
67
|
|
|
68
|
-
class CompactionParseError extends Schema.TaggedErrorClass<CompactionParseError>()(
|
|
68
|
+
class CompactionParseError extends Schema.TaggedErrorClass<CompactionParseError>()(ERROR_TAGS.CompactionParseError, {
|
|
69
69
|
message: Schema.String,
|
|
70
70
|
cause: Schema.optional(Schema.Defect),
|
|
71
71
|
}) {}
|
|
@@ -88,9 +88,11 @@ export interface CompactionOutput {
|
|
|
88
88
|
summary: string
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
type ContextCompactionRunnerError = { readonly _tag: string; readonly message?: string }
|
|
92
|
+
|
|
91
93
|
export type ContextCompactionRunner = (
|
|
92
94
|
params: ContextCompactionRunnerParams,
|
|
93
|
-
) => Effect.Effect<CompactionOutput,
|
|
95
|
+
) => Effect.Effect<CompactionOutput, ContextCompactionRunnerError, never>
|
|
94
96
|
|
|
95
97
|
export interface CreateContextCompactionRuntimeOptions {
|
|
96
98
|
runCompacter: ContextCompactionRunner
|
|
@@ -342,22 +344,29 @@ export function createContextCompactionRuntime(
|
|
|
342
344
|
const chunks = splitByCharBudget(params.newMessages, compactionChunkMaxChars)
|
|
343
345
|
const initialSummary = normalizeSummary(params.previousSummary)
|
|
344
346
|
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
const stepSummary = (state: {
|
|
348
|
+
summary: string
|
|
349
|
+
index: number
|
|
350
|
+
}): Effect.Effect<{ summary: string; index: number }, CompactionError> =>
|
|
351
|
+
state.index >= chunks.length
|
|
352
|
+
? Effect.succeed(state)
|
|
353
|
+
: Effect.gen(function* () {
|
|
351
354
|
const chunk = chunks[state.index]
|
|
352
355
|
const transcript = toCompactionTranscript(chunk)
|
|
353
356
|
const output = yield* options.runCompacter({ previousSummary: state.summary, chunk, transcript }).pipe(
|
|
354
|
-
Effect.mapError(
|
|
357
|
+
Effect.mapError(
|
|
358
|
+
(error) =>
|
|
359
|
+
new CompactionError({
|
|
360
|
+
message: error.message ?? `Context compaction runner failed with ${error._tag}.`,
|
|
361
|
+
cause: error,
|
|
362
|
+
}),
|
|
363
|
+
),
|
|
355
364
|
Effect.retry(COMPACTION_RUNNER_RETRY_OPTIONS),
|
|
356
365
|
)
|
|
357
|
-
return { summary: normalizeSummary(output.summary), index: state.index + 1 }
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
)
|
|
366
|
+
return yield* stepSummary({ summary: normalizeSummary(output.summary), index: state.index + 1 })
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const finalSummary = yield* stepSummary({ summary: initialSummary, index: 0 })
|
|
361
370
|
|
|
362
371
|
return { summary: finalSummary.summary }
|
|
363
372
|
})
|
|
@@ -419,59 +428,62 @@ export function createContextCompactionRuntime(
|
|
|
419
428
|
done: false,
|
|
420
429
|
}
|
|
421
430
|
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
Effect.gen(function* () {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
431
|
+
const stepHistory = (state: CompactionLoopState): Effect.Effect<CompactionLoopState, CompactionError> =>
|
|
432
|
+
state.done
|
|
433
|
+
? Effect.succeed(state)
|
|
434
|
+
: Effect.gen(function* () {
|
|
435
|
+
const assessment = shouldCompactHistory({
|
|
436
|
+
summaryText: state.summaryText,
|
|
437
|
+
liveMessages: state.remainingMessages,
|
|
438
|
+
contextSize: params.contextSize,
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
if (!assessment.shouldCompact) {
|
|
442
|
+
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const boundary = Math.max(0, state.remainingMessages.length - params.tailMessageCount)
|
|
446
|
+
if (boundary <= 0) {
|
|
447
|
+
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const candidatePrefix = state.remainingMessages.slice(0, boundary)
|
|
451
|
+
const messagesToCompact = candidatePrefix.filter((message) => !readIsCompacted(message))
|
|
452
|
+
const contextMessages = messagesToCompact
|
|
453
|
+
.map(toContextMessageFromChatMessage)
|
|
454
|
+
.filter((message) => compactWhitespace(message.text).length > 0)
|
|
455
|
+
const sourceText = toCompactionTranscript(contextMessages)
|
|
456
|
+
|
|
457
|
+
if (!compactWhitespace(sourceText)) {
|
|
458
|
+
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const compacted = yield* compactContextMessagesEffect({
|
|
462
|
+
previousSummary: state.summaryText,
|
|
463
|
+
newMessages: contextMessages,
|
|
464
|
+
})
|
|
465
|
+
const rolledSummary = yield* rollupSummaryIfOversizedEffect(normalizeSummary(compacted.summary))
|
|
466
|
+
|
|
467
|
+
if (rolledSummary.length >= sourceText.length) {
|
|
468
|
+
return yield* new CompactionError({
|
|
469
|
+
message: 'Compaction summary is not shorter than compacted source',
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return yield* stepHistory({
|
|
474
|
+
summaryText: rolledSummary,
|
|
475
|
+
remainingMessages: state.remainingMessages.slice(boundary),
|
|
476
|
+
compactedMessages: [
|
|
477
|
+
...state.compactedMessages,
|
|
478
|
+
...candidatePrefix.map((message) => markMessageCompacted(message, now)),
|
|
479
|
+
],
|
|
480
|
+
lastCompactedMessageId: candidatePrefix.at(-1)?.id ?? state.lastCompactedMessageId,
|
|
481
|
+
estimatedTokens: assessment.estimatedTokens,
|
|
482
|
+
done: false,
|
|
483
|
+
})
|
|
430
484
|
})
|
|
431
485
|
|
|
432
|
-
|
|
433
|
-
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const boundary = Math.max(0, state.remainingMessages.length - params.tailMessageCount)
|
|
437
|
-
if (boundary <= 0) {
|
|
438
|
-
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const candidatePrefix = state.remainingMessages.slice(0, boundary)
|
|
442
|
-
const messagesToCompact = candidatePrefix.filter((message) => !readIsCompacted(message))
|
|
443
|
-
const contextMessages = messagesToCompact
|
|
444
|
-
.map(toContextMessageFromChatMessage)
|
|
445
|
-
.filter((message) => compactWhitespace(message.text).length > 0)
|
|
446
|
-
const sourceText = toCompactionTranscript(contextMessages)
|
|
447
|
-
|
|
448
|
-
if (!compactWhitespace(sourceText)) {
|
|
449
|
-
return { ...state, estimatedTokens: assessment.estimatedTokens, done: true }
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const compacted = yield* compactContextMessagesEffect({
|
|
453
|
-
previousSummary: state.summaryText,
|
|
454
|
-
newMessages: contextMessages,
|
|
455
|
-
})
|
|
456
|
-
const rolledSummary = yield* rollupSummaryIfOversizedEffect(normalizeSummary(compacted.summary))
|
|
457
|
-
|
|
458
|
-
if (rolledSummary.length >= sourceText.length) {
|
|
459
|
-
return yield* new CompactionError({ message: 'Compaction summary is not shorter than compacted source' })
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return {
|
|
463
|
-
summaryText: rolledSummary,
|
|
464
|
-
remainingMessages: state.remainingMessages.slice(boundary),
|
|
465
|
-
compactedMessages: [
|
|
466
|
-
...state.compactedMessages,
|
|
467
|
-
...candidatePrefix.map((message) => markMessageCompacted(message, now)),
|
|
468
|
-
],
|
|
469
|
-
lastCompactedMessageId: candidatePrefix.at(-1)?.id ?? state.lastCompactedMessageId,
|
|
470
|
-
estimatedTokens: assessment.estimatedTokens,
|
|
471
|
-
done: false,
|
|
472
|
-
}
|
|
473
|
-
}),
|
|
474
|
-
})
|
|
486
|
+
const finalState = yield* stepHistory(initialState)
|
|
475
487
|
|
|
476
488
|
return buildExitResult(finalState)
|
|
477
489
|
})
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { Layer as LayerType } from 'effect'
|
|
11
11
|
import { Layer } from 'effect'
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import type { AiGatewayModelsTag, AiGatewayTag, RuntimeBridgeTag } from '../ai-gateway/ai-gateway'
|
|
14
14
|
import { EmbeddingCacheLive } from '../ai/embedding-cache'
|
|
15
15
|
import type { buildInfrastructureLayer } from '../effect/layers'
|
|
16
16
|
import { LotaQueuesLive } from '../queues/queues.service'
|
|
@@ -85,12 +85,18 @@ function provide<A, E, R extends RCtx, RCtx, E2>(
|
|
|
85
85
|
|
|
86
86
|
type InfrastructureLayer = ReturnType<typeof buildInfrastructureLayer>
|
|
87
87
|
|
|
88
|
+
type BridgeLayer = LayerType.Layer<AiGatewayTag | AiGatewayModelsTag | RuntimeBridgeTag, never, never>
|
|
89
|
+
|
|
88
90
|
/**
|
|
89
91
|
* Compose the domain service layer tree on top of the supplied infrastructure
|
|
90
|
-
* layer.
|
|
91
|
-
*
|
|
92
|
+
* layer. `bridgeLayer` provides `AiGatewayModelsTag` and `RuntimeBridgeTag`
|
|
93
|
+
* — these are constructed at the host boundary (`createLotaRuntime`) via
|
|
94
|
+
* `Deferred` because they reference the `ManagedRuntime` that is being built.
|
|
95
|
+
* They are threaded into the domain context so service layers that need them
|
|
96
|
+
* can `yield*` the tags directly instead of prop-drilling `runPromise`.
|
|
92
97
|
*/
|
|
93
|
-
export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer) {
|
|
98
|
+
export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer, bridgeLayer: BridgeLayer) {
|
|
99
|
+
const baseCtx = Layer.mergeAll(infrastructureLayer, bridgeLayer)
|
|
94
100
|
const tier0 = provide(
|
|
95
101
|
Layer.mergeAll(
|
|
96
102
|
BackgroundWorkServiceLive,
|
|
@@ -103,11 +109,11 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
|
|
|
103
109
|
PlanBuilderServiceLive,
|
|
104
110
|
WriteIntentValidatorServiceLive,
|
|
105
111
|
),
|
|
106
|
-
|
|
112
|
+
baseCtx,
|
|
107
113
|
)
|
|
108
114
|
const ctx0 = Layer.mergeAll(
|
|
109
|
-
|
|
110
|
-
provide(Layer.mergeAll(
|
|
115
|
+
baseCtx,
|
|
116
|
+
provide(Layer.mergeAll(EmbeddingCacheLive, FirecrawlLive, HelperModelLive), baseCtx),
|
|
111
117
|
tier0,
|
|
112
118
|
)
|
|
113
119
|
|
|
@@ -26,7 +26,7 @@ function formatExecutionPlansForPrompt(plans: SerializableExecutionPlan[]): stri
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export class ExecutionPlanCacheError extends Schema.TaggedErrorClass<ExecutionPlanCacheError>()(
|
|
29
|
-
'ExecutionPlanCacheError',
|
|
29
|
+
'@lota-sdk/core/ExecutionPlanCacheError',
|
|
30
30
|
{ message: Schema.String, cause: Schema.Defect },
|
|
31
31
|
) {}
|
|
32
32
|
|
|
@@ -42,7 +42,11 @@ export function buildExecutionPlanInstructionSections(plans: SerializableExecuti
|
|
|
42
42
|
export function createExecutionPlanInstructionSectionCache(params: {
|
|
43
43
|
disabled?: boolean
|
|
44
44
|
loadPlansEffect: () => Effect.Effect<SerializableExecutionPlan[], ExecutionPlanCacheError>
|
|
45
|
+
/** Promise adapter used by the `getPlans()` / `getSections()` Promise API.
|
|
46
|
+
* Required so this module never reaches for an ambient `Effect.runPromise`. */
|
|
47
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>
|
|
45
48
|
}) {
|
|
49
|
+
const runPromise = params.runPromise
|
|
46
50
|
const [getPlansCachedEffect, invalidatePlansEffect] = Effect.runSync(
|
|
47
51
|
Effect.cachedInvalidateWithTTL(
|
|
48
52
|
Effect.suspend(() => (params.disabled ? Effect.succeed([]) : params.loadPlansEffect())),
|
|
@@ -75,10 +79,10 @@ export function createExecutionPlanInstructionSectionCache(params: {
|
|
|
75
79
|
getPlansEffect,
|
|
76
80
|
getSectionsEffect,
|
|
77
81
|
getPlans() {
|
|
78
|
-
return
|
|
82
|
+
return runPromise(getPlansEffect())
|
|
79
83
|
},
|
|
80
84
|
getSections() {
|
|
81
|
-
return
|
|
85
|
+
return runPromise(getSectionsEffect())
|
|
82
86
|
},
|
|
83
87
|
}
|
|
84
88
|
}
|
|
@@ -56,30 +56,6 @@ function readHeadingTitle(line: string): string | null {
|
|
|
56
56
|
return null
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function readLeadLineTitle(line: string): string | null {
|
|
60
|
-
const trimmed = stripMarkdownTitleDecorators(line)
|
|
61
|
-
if (!trimmed) return null
|
|
62
|
-
if (trimmed.length > 90) return null
|
|
63
|
-
if (/[.!?]$/.test(trimmed)) return null
|
|
64
|
-
return trimmed
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function readFirstCompleteSentence(chunk: string): string | null {
|
|
68
|
-
const compact = normalizeWhitespace(chunk)
|
|
69
|
-
if (!compact) return null
|
|
70
|
-
const sentenceMatch = compact.match(/^(.+?[.!?])(?:\s|$)/)
|
|
71
|
-
if (sentenceMatch?.[1]) {
|
|
72
|
-
return sentenceMatch[1]
|
|
73
|
-
}
|
|
74
|
-
return null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isStableReasoningChunk(chunk: string, isLastChunk: boolean, isFinal: boolean): boolean {
|
|
78
|
-
if (!isLastChunk || isFinal) return true
|
|
79
|
-
if (chunk.includes('\n')) return true
|
|
80
|
-
return /[.!?:]\s*$/.test(chunk.trim())
|
|
81
|
-
}
|
|
82
|
-
|
|
83
59
|
export function extractThinkingTitlesFromReasoning(params: { text: string; isFinal?: boolean }): string[] {
|
|
84
60
|
const cleaned = sanitizeReasoningText(params.text).trim()
|
|
85
61
|
if (cleaned.length === 0) return []
|
|
@@ -91,35 +67,16 @@ export function extractThinkingTitlesFromReasoning(params: { text: string; isFin
|
|
|
91
67
|
|
|
92
68
|
const titles: string[] = []
|
|
93
69
|
|
|
94
|
-
for (const
|
|
95
|
-
const
|
|
96
|
-
if (!isStableReasoningChunk(chunk, isLastChunk, params.isFinal === true)) {
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const lines = chunk
|
|
70
|
+
for (const chunk of chunks) {
|
|
71
|
+
const firstLine = chunk
|
|
101
72
|
.split('\n')
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
if (
|
|
73
|
+
.find((line) => line.trim().length > 0)
|
|
74
|
+
?.trim()
|
|
75
|
+
if (!firstLine) continue
|
|
105
76
|
|
|
106
|
-
const headingTitle = readHeadingTitle(
|
|
77
|
+
const headingTitle = readHeadingTitle(firstLine)
|
|
107
78
|
if (headingTitle) {
|
|
108
79
|
titles.push(normalizeThinkingTitle(headingTitle))
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (lines.length > 1 || params.isFinal === true) {
|
|
113
|
-
const leadLineTitle = readLeadLineTitle(lines[0])
|
|
114
|
-
if (leadLineTitle) {
|
|
115
|
-
titles.push(normalizeThinkingTitle(leadLineTitle))
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const sentenceTitle = readFirstCompleteSentence(chunk)
|
|
121
|
-
if (sentenceTitle) {
|
|
122
|
-
titles.push(normalizeThinkingTitle(sentenceTitle))
|
|
123
80
|
}
|
|
124
81
|
}
|
|
125
82
|
|
|
@@ -5,7 +5,7 @@ import { nowDate } from '../../utils/date-time'
|
|
|
5
5
|
import { compactWhitespace } from '../../utils/string'
|
|
6
6
|
|
|
7
7
|
export class MemoryBlockCompactError extends Schema.TaggedErrorClass<MemoryBlockCompactError>()(
|
|
8
|
-
'MemoryBlockCompactError',
|
|
8
|
+
'@lota-sdk/core/MemoryBlockCompactError',
|
|
9
9
|
{ message: Schema.String, cause: Schema.Defect },
|
|
10
10
|
) {}
|
|
11
11
|
|
|
@@ -65,7 +65,7 @@ export interface CompactMemoryBlockEntriesParams {
|
|
|
65
65
|
compact: (params: {
|
|
66
66
|
previousSummary: string
|
|
67
67
|
newEntriesText: string
|
|
68
|
-
}) =>
|
|
68
|
+
}) => Effect.Effect<string, MemoryBlockCompactError>
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export interface CompactMemoryBlockEntriesResult {
|
|
@@ -119,13 +119,7 @@ export function compactMemoryBlockEntries(
|
|
|
119
119
|
while (entries.length >= params.triggerEntries) {
|
|
120
120
|
const chunk = entries.slice(0, params.chunkEntries)
|
|
121
121
|
const compactParams = { previousSummary: summary, newEntriesText: toMemoryBlockEntriesSource(chunk) }
|
|
122
|
-
const
|
|
123
|
-
const raw = yield* Effect.isEffect(value)
|
|
124
|
-
? value
|
|
125
|
-
: Effect.tryPromise({
|
|
126
|
-
try: () => Promise.resolve(value),
|
|
127
|
-
catch: (cause) => new MemoryBlockCompactError({ message: 'compact callback failed', cause }),
|
|
128
|
-
})
|
|
122
|
+
const raw = yield* params.compact(compactParams)
|
|
129
123
|
const nextSummary = raw.trim()
|
|
130
124
|
|
|
131
125
|
if (!nextSummary) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Effect, Schema } from 'effect'
|
|
2
2
|
|
|
3
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
4
|
+
|
|
3
5
|
export const ORG_SCOPE_PREFIX = 'org'
|
|
4
6
|
|
|
5
7
|
const SCOPE_ID_MAX_LENGTH = 255
|
|
6
8
|
const SCOPE_PREFIX_REGEX = /^[a-z][a-z0-9_]*$/
|
|
7
9
|
|
|
8
|
-
export class MemoryScopeError extends Schema.TaggedErrorClass<MemoryScopeError>()(
|
|
10
|
+
export class MemoryScopeError extends Schema.TaggedErrorClass<MemoryScopeError>()(ERROR_TAGS.MemoryScopeError, {
|
|
9
11
|
message: Schema.String,
|
|
10
12
|
}) {}
|
|
11
13
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Schema, Effect } from 'effect'
|
|
2
2
|
|
|
3
3
|
import type { RecordIdRef } from '../db/record-id'
|
|
4
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
4
5
|
import type { LotaRuntimeAdapters, LotaRuntimeIndexedRepositoriesContext } from './runtime-extensions'
|
|
5
6
|
|
|
6
7
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
7
8
|
return typeof value === 'object' && value !== null
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
class PluginResolutionError extends Schema.TaggedErrorClass<PluginResolutionError>()(
|
|
11
|
+
class PluginResolutionError extends Schema.TaggedErrorClass<PluginResolutionError>()(ERROR_TAGS.PluginResolutionError, {
|
|
11
12
|
stage: Schema.Literals([
|
|
12
13
|
'configuration',
|
|
13
14
|
'linear-installation',
|
|
@@ -3,6 +3,7 @@ import { Schema, Effect } from 'effect'
|
|
|
3
3
|
|
|
4
4
|
import type { ResolvedAgentConfig } from '../config/agent-defaults'
|
|
5
5
|
import type { RecordIdRef } from '../db/record-id'
|
|
6
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
6
7
|
import { AgentConfigServiceTag, RuntimeAdaptersServiceTag } from '../effect/services'
|
|
7
8
|
import { LotaQueuesServiceTag } from '../queues/queues.service'
|
|
8
9
|
import { RecentActivityServiceTag } from '../services/recent-activity.service'
|
|
@@ -27,10 +28,10 @@ import {
|
|
|
27
28
|
toHistoryMessages,
|
|
28
29
|
} from './thread-chat-helpers'
|
|
29
30
|
|
|
30
|
-
class PostTurnSideEffectsError extends Schema.TaggedErrorClass<PostTurnSideEffectsError>()(
|
|
31
|
-
|
|
32
|
-
cause: Schema.Defect,
|
|
33
|
-
|
|
31
|
+
class PostTurnSideEffectsError extends Schema.TaggedErrorClass<PostTurnSideEffectsError>()(
|
|
32
|
+
ERROR_TAGS.PostTurnSideEffectsError,
|
|
33
|
+
{ message: Schema.String, cause: Schema.Defect },
|
|
34
|
+
) {}
|
|
34
35
|
|
|
35
36
|
function tryPostTurnSideEffect<A>(
|
|
36
37
|
message: string,
|
|
@@ -214,7 +215,7 @@ export const runPostTurnSideEffects = Effect.fn('PostTurnSideEffects.run')(funct
|
|
|
214
215
|
() => {
|
|
215
216
|
const enqueuePostChatOrgAction = runtimeAdapters.enqueuePostChatOrgAction
|
|
216
217
|
if (!enqueuePostChatOrgAction) {
|
|
217
|
-
return
|
|
218
|
+
return Promise.resolve()
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
const sourceCreatedAt = referenceUserMessage.metadata?.createdAt ?? nowEpochMillis()
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Schema, Effect } from 'effect'
|
|
2
2
|
|
|
3
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
4
|
+
|
|
3
5
|
interface ScopedRetrievalTask<TCandidate, E = never> {
|
|
4
6
|
scopeTag: string
|
|
5
|
-
retrieve: () =>
|
|
7
|
+
retrieve: () => Effect.Effect<TCandidate[], E>
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
interface ScopedRetrievalResult<TCandidate> {
|
|
@@ -10,7 +12,7 @@ interface ScopedRetrievalResult<TCandidate> {
|
|
|
10
12
|
candidates: TCandidate[]
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
class ScopedRetrievalError extends Schema.TaggedErrorClass<ScopedRetrievalError>()(
|
|
15
|
+
class ScopedRetrievalError extends Schema.TaggedErrorClass<ScopedRetrievalError>()(ERROR_TAGS.ScopedRetrievalError, {
|
|
14
16
|
scopeTag: Schema.String,
|
|
15
17
|
message: Schema.String,
|
|
16
18
|
cause: Schema.Defect,
|
|
@@ -26,24 +28,10 @@ export function executeScopedRetrieval<TCandidate, E>(
|
|
|
26
28
|
return Effect.forEach(
|
|
27
29
|
tasks,
|
|
28
30
|
(task) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return result.pipe(
|
|
34
|
-
Effect.mapError((cause) => toScopedRetrievalError(task.scopeTag, cause)),
|
|
35
|
-
Effect.map((candidates) => ({ scopeTag: task.scopeTag, candidates })),
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return Effect.tryPromise({
|
|
40
|
-
try: () => result,
|
|
41
|
-
catch: (cause) => toScopedRetrievalError(task.scopeTag, cause),
|
|
42
|
-
}).pipe(Effect.map((candidates) => ({ scopeTag: task.scopeTag, candidates })))
|
|
43
|
-
} catch (cause) {
|
|
44
|
-
return Effect.fail(toScopedRetrievalError(task.scopeTag, cause))
|
|
45
|
-
}
|
|
46
|
-
}),
|
|
31
|
+
task.retrieve().pipe(
|
|
32
|
+
Effect.mapError((cause) => toScopedRetrievalError(task.scopeTag, cause)),
|
|
33
|
+
Effect.map((candidates) => ({ scopeTag: task.scopeTag, candidates })),
|
|
34
|
+
),
|
|
47
35
|
{ concurrency: 'unbounded' },
|
|
48
36
|
)
|
|
49
37
|
}
|
|
@@ -105,16 +105,10 @@ export interface LotaRuntimeSocialChatConfig {
|
|
|
105
105
|
slack?: LotaSocialChatSlackConfig
|
|
106
106
|
historyRedisKeyPrefix?: string
|
|
107
107
|
stateRedisKeyPrefix?: string
|
|
108
|
-
resolveContext: (
|
|
109
|
-
|
|
110
|
-
) => LotaSocialChatResolvedContext | Promise<LotaSocialChatResolvedContext>
|
|
111
|
-
buildAgentTools: (params: BuildSocialChatAgentToolsParams) => ToolSet | Promise<ToolSet>
|
|
108
|
+
resolveContext: (params: LotaSocialChatResolveContextParams) => Promise<LotaSocialChatResolvedContext>
|
|
109
|
+
buildAgentTools: (params: BuildSocialChatAgentToolsParams) => Promise<ToolSet>
|
|
112
110
|
getConsultParticipants?:
|
|
113
|
-
| ((params: {
|
|
114
|
-
workspaceId: RecordIdRef
|
|
115
|
-
workspaceIdString: string
|
|
116
|
-
platform: 'slack'
|
|
117
|
-
}) => string[] | Promise<string[]>)
|
|
111
|
+
| ((params: { workspaceId: RecordIdRef; workspaceIdString: string; platform: 'slack' }) => Promise<string[]>)
|
|
118
112
|
| undefined
|
|
119
113
|
}
|
|
120
114
|
|
|
@@ -35,12 +35,10 @@ export interface LotaRuntimeProfileProjection {
|
|
|
35
35
|
|
|
36
36
|
export interface LotaRuntimeWorkspaceProvider {
|
|
37
37
|
getWorkspace(workspaceId: RecordIdRef): Promise<Record<string, unknown>>
|
|
38
|
-
getLifecycleState?(
|
|
39
|
-
workspace: Record<string, unknown>,
|
|
40
|
-
): Promise<LotaRuntimeWorkspaceLifecycleState> | LotaRuntimeWorkspaceLifecycleState
|
|
38
|
+
getLifecycleState?(workspace: Record<string, unknown>): Promise<LotaRuntimeWorkspaceLifecycleState>
|
|
41
39
|
readProfileProjectionState?(
|
|
42
40
|
workspace: Record<string, unknown>,
|
|
43
|
-
): Promise<LotaRuntimeWorkspaceProjectionState | undefined>
|
|
41
|
+
): Promise<LotaRuntimeWorkspaceProjectionState | undefined>
|
|
44
42
|
buildPromptSummary?(workspaceId: RecordIdRef): Promise<string | undefined>
|
|
45
43
|
listRecentDomainEvents?(workspaceId: RecordIdRef, limit?: number): Promise<Array<Record<string, unknown>>>
|
|
46
44
|
hasActiveKnowledgeSources?(workspaceId: string): Promise<boolean>
|
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The plugin connect/disconnect helpers run inside the supplied
|
|
5
5
|
* `ManagedRuntime` so spans, logging, and layer context stay consistent with
|
|
6
|
-
* the rest of the SDK runtime.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* does not preserve the SDK runtime context — by then the runtime is being
|
|
10
|
-
* torn down.
|
|
6
|
+
* the rest of the SDK runtime. Disconnect runs its teardown sequence as a
|
|
7
|
+
* plain `Effect.runPromise` — the services it yields have no layer
|
|
8
|
+
* requirements because they are closure-captured shutdown thunks.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
11
|
import type { ManagedRuntime } from 'effect'
|
|
14
12
|
import { Effect } from 'effect'
|
|
15
13
|
|
|
16
|
-
import {
|
|
17
|
-
import { effectTryPromise } from '../effect/helpers'
|
|
14
|
+
import { RuntimeLifecycleError } from '../effect/errors'
|
|
18
15
|
import type { LotaPlugin } from './plugin-types'
|
|
19
16
|
|
|
20
17
|
// eslint-disable-next-line typescript-eslint/no-explicit-any -- ManagedRuntime is contravariant in R; `any` is the only valid wildcard
|
|
@@ -24,6 +21,26 @@ function getPluginLifecycleServices(plugin: LotaPlugin): Record<string, unknown>
|
|
|
24
21
|
return plugin.services
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
function toRuntimeLifecycleError(
|
|
25
|
+
operation: RuntimeLifecycleError['operation'],
|
|
26
|
+
cause: unknown,
|
|
27
|
+
pluginName?: string,
|
|
28
|
+
): RuntimeLifecycleError {
|
|
29
|
+
const detail = pluginName ? ` "${pluginName}"` : ''
|
|
30
|
+
const action =
|
|
31
|
+
operation === 'connect-plugin-database'
|
|
32
|
+
? `connect plugin database${detail}`
|
|
33
|
+
: operation === 'disconnect-plugin-database'
|
|
34
|
+
? `disconnect plugin database${detail}`
|
|
35
|
+
: operation === 'shutdown-social-chat'
|
|
36
|
+
? 'shut down social chat'
|
|
37
|
+
: operation === 'disconnect-plugin-databases'
|
|
38
|
+
? 'disconnect plugin databases'
|
|
39
|
+
: 'dispose managed runtime'
|
|
40
|
+
|
|
41
|
+
return new RuntimeLifecycleError({ operation, message: `Failed to ${action}.`, cause })
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
/**
|
|
28
45
|
* Build a plugin database connector that iterates the configured plugins and
|
|
29
46
|
* calls each `services.connectDatabase()` once, tracking completion in
|
|
@@ -49,7 +66,10 @@ export function createPluginDatabaseConnector(
|
|
|
49
66
|
}
|
|
50
67
|
|
|
51
68
|
const connectDatabaseFn = connectDatabase as (this: typeof services) => Promise<void>
|
|
52
|
-
yield*
|
|
69
|
+
yield* Effect.tryPromise({
|
|
70
|
+
try: () => connectDatabaseFn.call(services),
|
|
71
|
+
catch: (cause) => toRuntimeLifecycleError('connect-plugin-database', cause, pluginName),
|
|
72
|
+
})
|
|
53
73
|
connectedPluginDatabases.add(pluginName)
|
|
54
74
|
}
|
|
55
75
|
}),
|
|
@@ -82,7 +102,10 @@ export function createPluginDatabaseDisconnector(
|
|
|
82
102
|
}
|
|
83
103
|
|
|
84
104
|
const disconnectDatabaseFn = disconnectDatabase as (this: typeof services) => Promise<void>
|
|
85
|
-
yield*
|
|
105
|
+
yield* Effect.tryPromise({
|
|
106
|
+
try: () => disconnectDatabaseFn.call(services),
|
|
107
|
+
catch: (cause) => toRuntimeLifecycleError('disconnect-plugin-database', cause, pluginName),
|
|
108
|
+
})
|
|
86
109
|
connectedPluginDatabases.delete(pluginName)
|
|
87
110
|
}
|
|
88
111
|
}),
|
|
@@ -91,7 +114,6 @@ export function createPluginDatabaseDisconnector(
|
|
|
91
114
|
|
|
92
115
|
interface CreateDisconnectInput {
|
|
93
116
|
managedRuntime: SdkManagedRuntime
|
|
94
|
-
runPromiseWithCurrentContext: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
|
|
95
117
|
socialChatShutdown: () => Promise<void>
|
|
96
118
|
disconnectPluginDatabases: () => Promise<void>
|
|
97
119
|
}
|
|
@@ -100,9 +122,15 @@ interface CreateDisconnectInput {
|
|
|
100
122
|
* Compose the runtime `disconnect()` function. The returned function is
|
|
101
123
|
* idempotent: the first call starts the shutdown sequence, subsequent calls
|
|
102
124
|
* return the same in-flight promise.
|
|
125
|
+
*
|
|
126
|
+
* Teardown order:
|
|
127
|
+
* 1. socialChat shutdown (user-facing bots must go quiet first)
|
|
128
|
+
* 2. plugin database disconnect (release external connections)
|
|
129
|
+
* 3. clear AI gateway runtime slot BEFORE dispose (no new fibers)
|
|
130
|
+
* 4. managedRuntime.dispose() (release all scoped layer resources)
|
|
103
131
|
*/
|
|
104
132
|
export function createRuntimeDisconnect(input: CreateDisconnectInput): () => Promise<void> {
|
|
105
|
-
const { managedRuntime,
|
|
133
|
+
const { managedRuntime, socialChatShutdown, disconnectPluginDatabases } = input
|
|
106
134
|
|
|
107
135
|
let disconnectPromise: Promise<void> | null = null
|
|
108
136
|
|
|
@@ -111,12 +139,24 @@ export function createRuntimeDisconnect(input: CreateDisconnectInput): () => Pro
|
|
|
111
139
|
return disconnectPromise
|
|
112
140
|
}
|
|
113
141
|
|
|
114
|
-
disconnectPromise =
|
|
142
|
+
disconnectPromise = Effect.runPromise(
|
|
115
143
|
Effect.gen(function* () {
|
|
116
|
-
yield* Effect.ignore(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
144
|
+
yield* Effect.ignore(
|
|
145
|
+
Effect.tryPromise({
|
|
146
|
+
try: () => socialChatShutdown(),
|
|
147
|
+
catch: (cause) => toRuntimeLifecycleError('shutdown-social-chat', cause),
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
yield* Effect.ignore(
|
|
151
|
+
Effect.tryPromise({
|
|
152
|
+
try: () => disconnectPluginDatabases(),
|
|
153
|
+
catch: (cause) => toRuntimeLifecycleError('disconnect-plugin-databases', cause),
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
yield* Effect.tryPromise({
|
|
157
|
+
try: () => managedRuntime.dispose(),
|
|
158
|
+
catch: (cause) => toRuntimeLifecycleError('dispose-managed-runtime', cause),
|
|
159
|
+
})
|
|
120
160
|
}),
|
|
121
161
|
)
|
|
122
162
|
return disconnectPromise
|