@lota-sdk/core 0.4.7 → 0.4.9
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 +11 -12
- package/src/ai/embedding-cache.ts +94 -22
- package/src/ai-gateway/ai-gateway.ts +738 -223
- package/src/config/agent-defaults.ts +176 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/constants.ts +8 -2
- package/src/config/logger.ts +286 -19
- package/src/config/model-constants.ts +1 -0
- package/src/config/thread-defaults.ts +33 -21
- package/src/create-runtime.ts +725 -383
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +856 -598
- package/src/db/memory.ts +398 -275
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +255 -0
- package/src/db/service.ts +726 -761
- package/src/db/startup.ts +140 -66
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +87 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +98 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/layers.ts +228 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +31 -0
- package/src/effect/services.ts +57 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +122 -71
- package/src/index.ts +46 -1
- package/src/openrouter/direct-provider.ts +29 -0
- package/src/queues/autonomous-job.queue.ts +130 -74
- package/src/queues/context-compaction.queue.ts +60 -15
- package/src/queues/delayed-node-promotion.queue.ts +52 -15
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +13 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
- package/src/queues/plan-scheduler.queue.ts +107 -31
- package/src/queues/post-chat-memory.queue.ts +66 -24
- package/src/queues/queue-factory.ts +142 -52
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +54 -9
- package/src/redis/connection.ts +84 -32
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +10 -0
- package/src/redis/stream-context.ts +84 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +4 -1
- package/src/runtime/agent-stream-helpers.ts +20 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +11 -7
- package/src/runtime/helper-model.ts +135 -48
- package/src/runtime/index.ts +7 -7
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
- package/src/runtime/plugin-resolution.ts +144 -24
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +197 -130
- package/src/runtime/retrieval-adapters.ts +38 -4
- package/src/runtime/runtime-config.ts +165 -61
- package/src/runtime/runtime-extensions.ts +21 -34
- package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
- package/src/runtime/social-chat/social-chat.ts +594 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +172 -94
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +329 -217
- package/src/services/artifact.service.ts +225 -148
- package/src/services/attachment.service.ts +137 -115
- package/src/services/autonomous-job.service.ts +888 -491
- package/src/services/chat-run-registry.service.ts +11 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +162 -90
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +256 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +80 -170
- package/src/services/graph-full-routing.ts +182 -0
- package/src/services/index.ts +18 -20
- package/src/services/institutional-memory.service.ts +220 -123
- package/src/services/learned-skill.service.ts +364 -259
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-org-memory.ts +39 -0
- package/src/services/memory/memory-preseeded.ts +80 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
- package/src/services/memory/memory.service.ts +692 -0
- package/src/services/memory/rerank.service.ts +209 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +17 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +144 -51
- package/src/services/ownership-dispatcher.service.ts +415 -264
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/plan/plan-approval.service.ts +102 -0
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +175 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +398 -0
- package/src/services/plan/plan-deadline.service.ts +547 -0
- package/src/services/plan/plan-event-delivery.service.ts +261 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +475 -0
- package/src/services/plan/plan-executor-helpers.ts +322 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1654 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +644 -0
- package/src/services/plan/plan-scheduler.service.ts +385 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +33 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +125 -0
- package/src/services/plugin-executor.service.ts +97 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +296 -230
- package/src/services/recent-activity-title.service.ts +65 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +176 -125
- package/src/services/system-executor.service.ts +91 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +369 -0
- package/src/services/thread/thread-listing.ts +198 -0
- package/src/services/thread/thread-memory-block.ts +117 -0
- package/src/services/thread/thread-message.service.ts +363 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
- package/src/services/thread/thread-turn-streaming.ts +402 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +343 -0
- package/src/services/thread/thread.service.ts +335 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +331 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +2 -2
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/memory-reranker.agent.ts +2 -2
- package/src/system-agents/memory.agent.ts +2 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
- package/src/system-agents/skill-extractor.agent.ts +2 -2
- package/src/system-agents/skill-manager.agent.ts +2 -2
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +220 -161
- package/src/tools/fetch-webpage.tool.ts +21 -17
- package/src/tools/firecrawl-client.ts +16 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +49 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +26 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +124 -83
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +17 -23
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +95 -16
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +186 -51
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +56 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -844
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-approval.service.ts +0 -83
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -1,1147 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
THREAD,
|
|
3
|
-
dataPartsSchemas,
|
|
4
|
-
messageMetadataSchema,
|
|
5
|
-
PlanNodeResultSubmissionSchema,
|
|
6
|
-
SUBMIT_PLAN_TURN_RESULT_TOOL_NAME,
|
|
7
|
-
toTimestamp,
|
|
8
|
-
withMessageCreatedAt,
|
|
9
|
-
} from '@lota-sdk/shared'
|
|
10
|
-
import type { ChatMessage, MessageMetadata, PlanNodeSpecRecord } from '@lota-sdk/shared'
|
|
11
|
-
import { convertToModelMessages, stepCountIs, tool as createTool, validateUIMessages } from 'ai'
|
|
12
|
-
import type { PrepareStepFunction, StopCondition, ToolLoopAgent, ToolSet, UIMessageStreamWriter } from 'ai'
|
|
13
|
-
|
|
14
|
-
import type { CoreThreadProfile } from '../config/agent-defaults'
|
|
15
|
-
import {
|
|
16
|
-
agentRoster,
|
|
17
|
-
buildAgentTools,
|
|
18
|
-
createAgent,
|
|
19
|
-
getLeadAgentId,
|
|
20
|
-
getCoreThreadProfile,
|
|
21
|
-
getAgentRuntimeConfig,
|
|
22
|
-
} from '../config/agent-defaults'
|
|
23
|
-
import { lotaDebugLogger } from '../config/debug-logger'
|
|
24
|
-
import { aiLogger } from '../config/logger'
|
|
25
|
-
import type { RecordIdRef } from '../db/record-id'
|
|
26
|
-
import { recordIdToString } from '../db/record-id'
|
|
27
|
-
import { TABLES } from '../db/tables'
|
|
28
|
-
import { enqueueContextCompaction } from '../queues/context-compaction.queue'
|
|
29
|
-
import { enqueueThreadTitleGeneration } from '../queues/title-generation.queue'
|
|
30
|
-
import { readRuntimeAgentIdentityOverrides, resolveRuntimeAgentDisplayName } from '../runtime/agent-identity-overrides'
|
|
31
|
-
import { OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES } from '../runtime/agent-runtime-policy'
|
|
32
|
-
import { createAgentMessageMetadata, createServerRunAbortController } from '../runtime/agent-stream-helpers'
|
|
33
|
-
import { hasApprovalRespondedParts } from '../runtime/approval-continuation'
|
|
34
|
-
import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataText } from '../runtime/chat-attachments'
|
|
35
|
-
import { hasMessageContent } from '../runtime/chat-message'
|
|
36
|
-
import { waitForCompactionIfNeeded } from '../runtime/chat-run-orchestration'
|
|
37
|
-
import { CONTEXT_WINDOW_TOKENS } from '../runtime/context-compaction-constants'
|
|
38
|
-
import { createExecutionPlanInstructionSectionCache } from '../runtime/execution-plan'
|
|
39
|
-
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
40
|
-
import { runPostTurnSideEffects } from '../runtime/post-turn-side-effects'
|
|
41
|
-
import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/runtime-extensions'
|
|
42
|
-
import {
|
|
43
|
-
asRecord,
|
|
44
|
-
collectCompletedConsultTeamMessages,
|
|
45
|
-
collectToolOutputErrors,
|
|
46
|
-
extractMessageText,
|
|
47
|
-
readInstructionSections,
|
|
48
|
-
readOptionalString,
|
|
49
|
-
toOptionalTrimmedString,
|
|
50
|
-
} from '../runtime/thread-chat-helpers'
|
|
51
|
-
import {
|
|
52
|
-
buildPlanTurnInstructionSections,
|
|
53
|
-
buildPlanTurnPromptMessage,
|
|
54
|
-
buildPlanTurnSubmitToolDescription,
|
|
55
|
-
} from '../runtime/thread-plan-turn'
|
|
56
|
-
import type { ThreadPlanTurnContext } from '../runtime/thread-plan-turn'
|
|
57
|
-
import { assembleThreadTurnContext } from '../runtime/thread-turn-context'
|
|
58
|
-
import { finalizeTurnRun } from '../runtime/turn-lifecycle'
|
|
59
|
-
import { chatRunRegistry } from '../services/chat-run-registry.service'
|
|
60
|
-
import type { NormalizedThread, ThreadRecord } from '../services/thread.types'
|
|
61
|
-
import { triageThreadMessage, checkForNextAgent } from '../system-agents/thread-router.agent'
|
|
62
|
-
import { safeEnqueue } from '../utils/async'
|
|
63
|
-
import { AppError } from '../utils/errors'
|
|
64
|
-
import { attachmentService } from './attachment.service'
|
|
65
|
-
import { listReadableUploadsFromChatMessages } from './chat-attachments.service'
|
|
66
|
-
import { contextCompactionRuntime } from './context-compaction-runtime.singleton'
|
|
67
|
-
import { executionPlanService } from './execution-plan.service'
|
|
68
|
-
import { learnedSkillService } from './learned-skill.service'
|
|
69
|
-
import { memoryService } from './memory.service'
|
|
70
|
-
import { planRunService } from './plan-run.service'
|
|
71
|
-
import { threadMessageService } from './thread-message.service'
|
|
72
|
-
import { ActiveThreadRunConflictError, threadService } from './thread.service'
|
|
73
|
-
|
|
74
|
-
type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
|
|
75
|
-
|
|
76
|
-
interface UIMessageStreamResult {
|
|
77
|
-
toUIMessageStream(options: Record<string, unknown>): ReadableStream<unknown>
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
|
|
81
|
-
return (
|
|
82
|
-
typeof value === 'object' &&
|
|
83
|
-
value !== null &&
|
|
84
|
-
typeof (value as { toUIMessageStream?: unknown }).toUIMessageStream === 'function'
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
|
|
89
|
-
|
|
90
|
-
async function waitForThreadCompactionIfNeeded(threadId: RecordIdRef): Promise<ThreadRecord> {
|
|
91
|
-
return waitForCompactionIfNeeded({
|
|
92
|
-
entityId: recordIdToString(threadId, TABLES.THREAD),
|
|
93
|
-
entityLabel: 'Thread',
|
|
94
|
-
loadEntity: () => threadService.getById(threadId),
|
|
95
|
-
isCompacting: (thread) => thread.isCompacting === true,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
class ThreadTurnError extends AppError {
|
|
100
|
-
constructor(
|
|
101
|
-
message: string,
|
|
102
|
-
readonly statusCode: 400 | 409,
|
|
103
|
-
) {
|
|
104
|
-
super(message, statusCode === 409 ? 'CONFLICT' : 'BAD_REQUEST', statusCode)
|
|
105
|
-
this.name = 'ThreadTurnError'
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function optionalInstructionSection(value: unknown): string[] | undefined {
|
|
110
|
-
const section = readOptionalString(value)
|
|
111
|
-
return section ? [section] : undefined
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function writeMultiAgentEvent(
|
|
115
|
-
writer: UIMessageStreamWriter<ChatMessage> | undefined,
|
|
116
|
-
event: {
|
|
117
|
-
phase: 'routing' | 'waiting-for-agent' | 'agent-message-persisted' | 'complete'
|
|
118
|
-
agentId?: string
|
|
119
|
-
agentName?: string
|
|
120
|
-
messageId?: string
|
|
121
|
-
note?: string
|
|
122
|
-
},
|
|
123
|
-
): void {
|
|
124
|
-
if (!writer) return
|
|
125
|
-
|
|
126
|
-
const chunk: ChatStreamChunk = {
|
|
127
|
-
type: 'data-multi-agent-event',
|
|
128
|
-
id: `multi-agent-${Bun.randomUUIDv7()}`,
|
|
129
|
-
data: event,
|
|
130
|
-
transient: true,
|
|
131
|
-
}
|
|
132
|
-
writer.write(chunk)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function applyPlanTurnToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpecRecord): ToolSet {
|
|
136
|
-
const blockedToolNames = new Set([...OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES, ...nodeSpec.toolPolicy.deny])
|
|
137
|
-
const allowList = nodeSpec.toolPolicy.allow.length > 0 ? new Set(nodeSpec.toolPolicy.allow) : null
|
|
138
|
-
|
|
139
|
-
return Object.fromEntries(
|
|
140
|
-
Object.entries(tools).filter(
|
|
141
|
-
([toolName]) =>
|
|
142
|
-
!blockedToolNames.has(toolName) &&
|
|
143
|
-
(toolName === SUBMIT_PLAN_TURN_RESULT_TOOL_NAME || allowList === null || allowList.has(toolName)),
|
|
144
|
-
),
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export interface ThreadTurnParams {
|
|
149
|
-
thread: NormalizedThread
|
|
150
|
-
threadRef: RecordIdRef
|
|
151
|
-
orgRef: RecordIdRef
|
|
152
|
-
userRef: RecordIdRef
|
|
153
|
-
userName?: string | null
|
|
154
|
-
agentIdOverride?: string
|
|
155
|
-
inputMessage: ChatMessage
|
|
156
|
-
skipInputMessagePersistence?: boolean
|
|
157
|
-
abortSignal?: AbortSignal
|
|
158
|
-
streamId?: string
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface ThreadApprovalContinuationParams {
|
|
162
|
-
thread: NormalizedThread
|
|
163
|
-
threadRef: RecordIdRef
|
|
164
|
-
orgRef: RecordIdRef
|
|
165
|
-
userRef: RecordIdRef
|
|
166
|
-
userName?: string | null
|
|
167
|
-
approvalMessages: ChatMessage[]
|
|
168
|
-
abortSignal?: AbortSignal
|
|
169
|
-
streamId?: string
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export interface ThreadPlanTurnParams {
|
|
173
|
-
thread: NormalizedThread
|
|
174
|
-
threadRef: RecordIdRef
|
|
175
|
-
orgRef: RecordIdRef
|
|
176
|
-
userRef: RecordIdRef
|
|
177
|
-
userName?: string | null
|
|
178
|
-
planTurn: ThreadPlanTurnContext
|
|
179
|
-
abortSignal?: AbortSignal
|
|
180
|
-
streamId?: string
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
type ThreadRunCoreParams = {
|
|
184
|
-
thread: NormalizedThread
|
|
185
|
-
threadRef: RecordIdRef
|
|
186
|
-
orgRef: RecordIdRef
|
|
187
|
-
userRef: RecordIdRef
|
|
188
|
-
userName?: string | null
|
|
189
|
-
agentIdOverride?: string
|
|
190
|
-
abortSignal?: AbortSignal
|
|
191
|
-
streamId?: string
|
|
192
|
-
} & (
|
|
193
|
-
| { kind: 'userTurn'; inputMessage: ChatMessage; skipInputMessagePersistence?: boolean }
|
|
194
|
-
| { kind: 'approvalContinuation'; approvalMessages: ChatMessage[] }
|
|
195
|
-
| { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
|
|
196
|
-
| { kind: 'planTurn'; planTurn: ThreadPlanTurnContext }
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
interface PreparedThreadTurn {
|
|
200
|
-
originalMessages: ChatMessage[]
|
|
201
|
-
run: (writer?: UIMessageStreamWriter<ChatMessage>) => Promise<PreparedThreadTurnResult>
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface PreparedThreadTurnResult {
|
|
205
|
-
inputMessageId?: string
|
|
206
|
-
assistantMessages: ChatMessage[]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function upsertChatHistoryMessage(messages: ChatMessage[], nextMessage: ChatMessage): ChatMessage[] {
|
|
210
|
-
const existingIndex = messages.findIndex((message) => message.id === nextMessage.id)
|
|
211
|
-
if (existingIndex === -1) {
|
|
212
|
-
return [...messages, nextMessage]
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const nextMessages = [...messages]
|
|
216
|
-
nextMessages[existingIndex] = nextMessage
|
|
217
|
-
return nextMessages
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
interface StreamAgentResponseContext {
|
|
221
|
-
turnHooks: ReturnType<typeof getTurnHooks>
|
|
222
|
-
thread: NormalizedThread
|
|
223
|
-
threadRef: RecordIdRef
|
|
224
|
-
orgRef: RecordIdRef
|
|
225
|
-
userRef: RecordIdRef
|
|
226
|
-
userName?: string | null
|
|
227
|
-
onboardingActive: boolean
|
|
228
|
-
linearInstalled: boolean
|
|
229
|
-
githubInstalled: boolean
|
|
230
|
-
buildContextResult: Record<string, unknown> | null
|
|
231
|
-
getExecutionPlanInstructionSections: () => Promise<string[] | undefined>
|
|
232
|
-
getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
|
|
233
|
-
getLearnedSkillsSection: (agentId: string, queryText?: string) => Promise<string | undefined>
|
|
234
|
-
promptContext: { systemWorkspaceDetails?: string }
|
|
235
|
-
retrievedKnowledgeSection: string | undefined
|
|
236
|
-
memoryBlock: string
|
|
237
|
-
hookInstructionSections: string[]
|
|
238
|
-
runAbortSignal: AbortSignal
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
interface StreamAgentResponseParams {
|
|
242
|
-
agentId: string
|
|
243
|
-
mode: 'direct' | 'fixedThreadMode' | 'threadMode'
|
|
244
|
-
messages: ChatMessage[]
|
|
245
|
-
tools: ToolSet
|
|
246
|
-
observer: {
|
|
247
|
-
run: <T>(fn: () => T | Promise<T>) => Promise<T>
|
|
248
|
-
recordError: (error: unknown) => void
|
|
249
|
-
recordAbort: (error: unknown) => void
|
|
250
|
-
}
|
|
251
|
-
skills?: string[]
|
|
252
|
-
additionalInstructionSections?: string[]
|
|
253
|
-
includeExecutionPlanTools?: boolean
|
|
254
|
-
writer?: UIMessageStreamWriter<ChatMessage>
|
|
255
|
-
stopWhen?: StopCondition<ToolSet> | Array<StopCondition<ToolSet>>
|
|
256
|
-
prepareStep?: PrepareStepFunction<ToolSet>
|
|
257
|
-
abortSignal?: AbortSignal
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function streamAgentResponse(
|
|
261
|
-
ctx: StreamAgentResponseContext,
|
|
262
|
-
streamParams: StreamAgentResponseParams,
|
|
263
|
-
): Promise<ChatMessage> {
|
|
264
|
-
const agentTimer = lotaDebugLogger.timer(`agent:${streamParams.agentId}`)
|
|
265
|
-
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(ctx.buildContextResult)
|
|
266
|
-
// Skip full plan state during plan turns — the plan-turn sections already have the active node contract
|
|
267
|
-
const executionPlanInstructionSections =
|
|
268
|
-
streamParams.includeExecutionPlanTools === false ? undefined : await ctx.getExecutionPlanInstructionSections()
|
|
269
|
-
agentTimer.step('get-execution-plan')
|
|
270
|
-
const agentResolution = asRecord(
|
|
271
|
-
await ctx.turnHooks.resolveAgent?.({
|
|
272
|
-
agentId: streamParams.agentId,
|
|
273
|
-
mode: streamParams.mode,
|
|
274
|
-
thread: ctx.thread,
|
|
275
|
-
threadRef: ctx.threadRef,
|
|
276
|
-
orgRef: ctx.orgRef,
|
|
277
|
-
userRef: ctx.userRef,
|
|
278
|
-
userName: ctx.userName,
|
|
279
|
-
onboardingActive: ctx.onboardingActive,
|
|
280
|
-
linearInstalled: ctx.linearInstalled,
|
|
281
|
-
githubInstalled: ctx.githubInstalled,
|
|
282
|
-
skills: streamParams.skills,
|
|
283
|
-
additionalInstructionSections: streamParams.additionalInstructionSections,
|
|
284
|
-
context: ctx.buildContextResult,
|
|
285
|
-
}),
|
|
286
|
-
)
|
|
287
|
-
agentTimer.step('hook:resolveAgent')
|
|
288
|
-
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
|
|
289
|
-
const latestUserMessage = [...streamParams.messages].reverse().find((message) => message.role === 'user')
|
|
290
|
-
const latestUserMessageText = latestUserMessage ? extractMessageText(latestUserMessage).trim() : undefined
|
|
291
|
-
const [preSeededMemoriesSection, learnedSkillsSection] = await Promise.all([
|
|
292
|
-
ctx.getPreSeededMemoriesSection(resolvedAgentId),
|
|
293
|
-
ctx.getLearnedSkillsSection(resolvedAgentId, latestUserMessageText),
|
|
294
|
-
])
|
|
295
|
-
agentTimer.step('parallel-fetch(memories+skills)')
|
|
296
|
-
const toolNames = new Set(Object.keys(streamParams.tools))
|
|
297
|
-
const hasRetrievalTools = [
|
|
298
|
-
'memorySearch',
|
|
299
|
-
'conversationSearch',
|
|
300
|
-
'queryKnowledge',
|
|
301
|
-
'researchTopic',
|
|
302
|
-
'fetchWebpage',
|
|
303
|
-
'inspectWebsite',
|
|
304
|
-
].some((toolName) => toolNames.has(toolName))
|
|
305
|
-
const hasDomainRoutingSkills =
|
|
306
|
-
(streamParams.skills ?? []).some((skill) => skill.startsWith('cpo-') || skill.startsWith('mentor-')) ||
|
|
307
|
-
resolvedAgentId === 'cpo' ||
|
|
308
|
-
resolvedAgentId === 'mentor'
|
|
309
|
-
const config = getAgentRuntimeConfig({
|
|
310
|
-
agentId: resolvedAgentId,
|
|
311
|
-
threadType: ctx.thread.type,
|
|
312
|
-
mode: streamParams.mode,
|
|
313
|
-
skills: streamParams.skills,
|
|
314
|
-
onboardingActive: ctx.onboardingActive,
|
|
315
|
-
linearInstalled: ctx.linearInstalled,
|
|
316
|
-
systemWorkspaceDetails: ctx.promptContext.systemWorkspaceDetails,
|
|
317
|
-
preSeededMemoriesSection,
|
|
318
|
-
retrievedKnowledgeSection: ctx.retrievedKnowledgeSection,
|
|
319
|
-
threadMemoryBlock: ctx.memoryBlock,
|
|
320
|
-
learnedSkillsSection,
|
|
321
|
-
userMessageText: latestUserMessageText,
|
|
322
|
-
ruleOptions: { includeMemr3Rule: hasRetrievalTools, includeDomainReasoningFallbackRule: hasDomainRoutingSkills },
|
|
323
|
-
additionalInstructionSections: mergeInstructionSections(
|
|
324
|
-
executionPlanInstructionSections,
|
|
325
|
-
streamParams.additionalInstructionSections,
|
|
326
|
-
ctx.hookInstructionSections,
|
|
327
|
-
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
328
|
-
optionalInstructionSection(agentResolution?.extraInstructions),
|
|
329
|
-
),
|
|
330
|
-
context: ctx.buildContextResult,
|
|
331
|
-
}) as Record<string, unknown>
|
|
332
|
-
agentTimer.step('build-agent-config')
|
|
333
|
-
const modelMessages = await convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true })
|
|
334
|
-
agentTimer.step('convert-model-messages')
|
|
335
|
-
const agent = createAgent[config.id as string]({
|
|
336
|
-
mode: streamParams.mode,
|
|
337
|
-
tools: streamParams.tools,
|
|
338
|
-
extraInstructions: config.extraInstructions,
|
|
339
|
-
maxRetries: 3,
|
|
340
|
-
stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
|
|
341
|
-
streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
|
|
342
|
-
prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
|
|
343
|
-
}) as ToolLoopAgent<never, ToolSet>
|
|
344
|
-
const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
|
|
345
|
-
agentTimer.step('agent-construction')
|
|
346
|
-
|
|
347
|
-
let result: unknown
|
|
348
|
-
try {
|
|
349
|
-
result = await streamParams.observer.run(() =>
|
|
350
|
-
agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
|
|
351
|
-
)
|
|
352
|
-
agentTimer.step('agent.stream()-resolved')
|
|
353
|
-
} catch (error) {
|
|
354
|
-
if (agentAbortSignal.aborted) {
|
|
355
|
-
streamParams.observer.recordAbort(error)
|
|
356
|
-
throw error
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
streamParams.observer.recordError(error)
|
|
360
|
-
throw error
|
|
361
|
-
}
|
|
362
|
-
if (!hasUIMessageStream(result)) {
|
|
363
|
-
throw new Error(`Agent run for ${resolvedAgentId} did not expose a UI message stream.`)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let responseMessage: ChatMessage | null = null
|
|
367
|
-
let resolveFinishedStream!: () => void
|
|
368
|
-
const finishedStream = new Promise<void>((resolve) => {
|
|
369
|
-
resolveFinishedStream = resolve
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
const uiStream = result.toUIMessageStream({
|
|
373
|
-
generateMessageId: () => Bun.randomUUIDv7(),
|
|
374
|
-
originalMessages: streamParams.messages,
|
|
375
|
-
sendReasoning: true,
|
|
376
|
-
sendSources: true,
|
|
377
|
-
messageMetadata: createAgentMessageMetadata({
|
|
378
|
-
agentId: resolvedAgentId,
|
|
379
|
-
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, resolvedAgentId),
|
|
380
|
-
}),
|
|
381
|
-
onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
|
|
382
|
-
responseMessage = withMessageCreatedAt(finishedResponseMessage, Date.now())
|
|
383
|
-
resolveFinishedStream()
|
|
384
|
-
},
|
|
385
|
-
}) as ReadableStream<ChatStreamChunk>
|
|
386
|
-
const reader = uiStream.getReader()
|
|
387
|
-
let firstChunkLogged = false
|
|
388
|
-
try {
|
|
389
|
-
for (;;) {
|
|
390
|
-
const { done, value } = await reader.read()
|
|
391
|
-
if (done) break
|
|
392
|
-
if (!firstChunkLogged) {
|
|
393
|
-
agentTimer.step('first-stream-chunk')
|
|
394
|
-
firstChunkLogged = true
|
|
395
|
-
}
|
|
396
|
-
if (streamParams.writer) {
|
|
397
|
-
streamParams.writer.write(value)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
} finally {
|
|
401
|
-
reader.releaseLock()
|
|
402
|
-
}
|
|
403
|
-
agentTimer.step('stream-complete')
|
|
404
|
-
|
|
405
|
-
await finishedStream
|
|
406
|
-
// responseMessage is set inside the stream callback — linter cannot track cross-callback assignment
|
|
407
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
408
|
-
if (responseMessage === null) {
|
|
409
|
-
throw new Error(`Agent run for ${resolvedAgentId} did not produce a response message.`)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
for (const toolError of collectToolOutputErrors({ responseMessage: responseMessage })) {
|
|
413
|
-
aiLogger.error`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return responseMessage
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export async function prepareThreadRunCore(params: ThreadRunCoreParams): Promise<PreparedThreadTurn> {
|
|
420
|
-
const { thread, threadRef, orgRef, userRef, userName } = params
|
|
421
|
-
const runtimeAdapters = getRuntimeAdapters()
|
|
422
|
-
const turnHooks = getTurnHooks()
|
|
423
|
-
const toolProviders = getToolProviders()
|
|
424
|
-
const workspaceProvider = runtimeAdapters.workspaceProvider
|
|
425
|
-
const orgIdString = recordIdToString(orgRef, TABLES.ORGANIZATION)
|
|
426
|
-
const userIdString = recordIdToString(userRef, TABLES.USER)
|
|
427
|
-
const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
|
|
428
|
-
|
|
429
|
-
const hydrateMessageFileUrls = (message: ChatMessage): ChatMessage => ({
|
|
430
|
-
...message,
|
|
431
|
-
parts: attachmentService.hydrateSignedFileUrlsInMessageParts({
|
|
432
|
-
parts: message.parts as Array<Record<string, unknown>>,
|
|
433
|
-
orgId: orgRef,
|
|
434
|
-
userId: userRef,
|
|
435
|
-
}) as ChatMessage['parts'],
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
let inputMessage: ChatMessage | undefined
|
|
439
|
-
const shouldPersistInputMessage = params.kind === 'userTurn' ? params.skipInputMessagePersistence !== true : false
|
|
440
|
-
const shouldProcessPostRunSideEffects =
|
|
441
|
-
params.kind !== 'planTurn' &&
|
|
442
|
-
(params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage)
|
|
443
|
-
if (params.kind === 'userTurn') {
|
|
444
|
-
inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage, Date.now()))
|
|
445
|
-
if (inputMessage.role !== 'user') {
|
|
446
|
-
throw new ThreadTurnError('Only user messages can be submitted to the thread runtime.', 400)
|
|
447
|
-
}
|
|
448
|
-
if (!hasMessageContent(inputMessage.parts)) {
|
|
449
|
-
throw new ThreadTurnError('Thread messages must include text or attachments.', 400)
|
|
450
|
-
}
|
|
451
|
-
if (thread.type === 'default' && !thread.agentId) {
|
|
452
|
-
throw new ThreadTurnError('Default threads require an assigned agent.', 400)
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const timer = lotaDebugLogger.timer('prepare')
|
|
457
|
-
|
|
458
|
-
// Start workspace fetch early unless approval handling will short-circuit the turn.
|
|
459
|
-
const workspacePromise =
|
|
460
|
-
params.kind !== 'approvalContinuation' && workspaceProvider
|
|
461
|
-
? workspaceProvider.getWorkspace(orgRef)
|
|
462
|
-
: Promise.resolve({})
|
|
463
|
-
|
|
464
|
-
const threadRecord = await waitForThreadCompactionIfNeeded(threadRef)
|
|
465
|
-
timer.step('compaction-gate')
|
|
466
|
-
// Plan turns run without the chat lease — they must not block or be blocked by user messages.
|
|
467
|
-
if (params.kind !== 'planTurn') {
|
|
468
|
-
if ((await threadService.hasActiveRunLease(threadRef)) || toOptionalTrimmedString(threadRecord.activeRunId)) {
|
|
469
|
-
const clearedStaleRun = await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
|
|
470
|
-
if (!clearedStaleRun) {
|
|
471
|
-
throw new ThreadTurnError('A chat run is already active.', 409)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn') {
|
|
477
|
-
const approvedAssistantMessage = [...params.approvalMessages]
|
|
478
|
-
.reverse()
|
|
479
|
-
.find((m) => m.role === 'assistant' && hasApprovalRespondedParts(m))
|
|
480
|
-
if (!approvedAssistantMessage) {
|
|
481
|
-
throw new ThreadTurnError('No approval-responded message found.', 400)
|
|
482
|
-
}
|
|
483
|
-
await threadMessageService.upsertMessages({ threadId: threadRef, messages: [approvedAssistantMessage] })
|
|
484
|
-
timer.step('persist-approval-message')
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const persistedCompactionCursor = toOptionalTrimmedString(threadRecord.lastCompactedMessageId) ?? undefined
|
|
488
|
-
const persistedLiveHistoryPromise = threadMessageService.listMessagesAfterCursor(threadRef, persistedCompactionCursor)
|
|
489
|
-
let recentHistoryPromise: Promise<ChatMessage[]> | null = null
|
|
490
|
-
const loadRecentHistory = async (): Promise<ChatMessage[]> => {
|
|
491
|
-
if (!recentHistoryPromise) {
|
|
492
|
-
recentHistoryPromise = threadMessageService
|
|
493
|
-
.listRecentMessages(threadRef, 64)
|
|
494
|
-
.then(async (persistedRecentHistory) => {
|
|
495
|
-
if (persistedRecentHistory.length === 0) {
|
|
496
|
-
return [] as ChatMessage[]
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const messages = await validateUIMessages<ChatMessage>({
|
|
500
|
-
messages: persistedRecentHistory,
|
|
501
|
-
metadataSchema: messageMetadataSchema,
|
|
502
|
-
dataSchemas: dataPartsSchemas,
|
|
503
|
-
})
|
|
504
|
-
return messages.map(hydrateMessageFileUrls)
|
|
505
|
-
})
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return await recentHistoryPromise
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
let userMessage: ChatMessage | undefined
|
|
512
|
-
if (inputMessage) {
|
|
513
|
-
userMessage = {
|
|
514
|
-
...inputMessage,
|
|
515
|
-
id: inputMessage.id,
|
|
516
|
-
role: 'user',
|
|
517
|
-
parts: inputMessage.parts,
|
|
518
|
-
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) },
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const persistedLiveHistory = await persistedLiveHistoryPromise
|
|
523
|
-
timer.step('fetch-history')
|
|
524
|
-
const liveHistory = await (persistedLiveHistory.length === 0
|
|
525
|
-
? Promise.resolve([] as ChatMessage[])
|
|
526
|
-
: validateUIMessages<ChatMessage>({
|
|
527
|
-
messages: persistedLiveHistory,
|
|
528
|
-
metadataSchema: messageMetadataSchema,
|
|
529
|
-
dataSchemas: dataPartsSchemas,
|
|
530
|
-
}).then((messages) => messages.map(hydrateMessageFileUrls)))
|
|
531
|
-
timer.step('validate+hydrate-history')
|
|
532
|
-
|
|
533
|
-
if (userMessage && shouldPersistInputMessage) {
|
|
534
|
-
await threadMessageService.upsertMessages({ threadId: threadRef, messages: [userMessage] })
|
|
535
|
-
timer.step('persist-user-message')
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const originalMessages = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
539
|
-
let allAssistantMessages: ChatMessage[] = []
|
|
540
|
-
const referenceUserMessage =
|
|
541
|
-
params.kind === 'planTurn'
|
|
542
|
-
? undefined
|
|
543
|
-
: params.kind === 'userTurn' && !shouldPersistInputMessage
|
|
544
|
-
? [...liveHistory].reverse().find((m) => m.role === 'user')
|
|
545
|
-
: (userMessage ?? [...liveHistory].reverse().find((m) => m.role === 'user'))
|
|
546
|
-
const messageText =
|
|
547
|
-
params.kind === 'planTurn'
|
|
548
|
-
? `${params.planTurn.nodeSpec.label}\n${params.planTurn.nodeSpec.objective}\n${params.planTurn.nodeSpec.instructions}`
|
|
549
|
-
: referenceUserMessage
|
|
550
|
-
? extractMessageText(referenceUserMessage).trim()
|
|
551
|
-
: ''
|
|
552
|
-
|
|
553
|
-
const respondedBy = recordIdToString(userRef, TABLES.USER)
|
|
554
|
-
if (params.kind === 'approvalContinuation') {
|
|
555
|
-
await executionPlanService.applyApprovalResponseFromMessages({
|
|
556
|
-
threadId: threadRef,
|
|
557
|
-
approvalMessages: params.approvalMessages,
|
|
558
|
-
respondedBy,
|
|
559
|
-
})
|
|
560
|
-
timer.step('approval-continuation')
|
|
561
|
-
|
|
562
|
-
return { originalMessages, run: async () => ({ inputMessageId: referenceUserMessage?.id, assistantMessages: [] }) }
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
params.kind === 'userTurn' &&
|
|
567
|
-
thread.type === 'group' &&
|
|
568
|
-
threadRecord.nameGenerated !== true &&
|
|
569
|
-
threadRecord.title === THREAD.DEFAULT_TITLE &&
|
|
570
|
-
messageText.length > 0
|
|
571
|
-
) {
|
|
572
|
-
void safeEnqueue(() => enqueueThreadTitleGeneration({ threadId: threadIdString, sourceText: messageText }), {
|
|
573
|
-
operationName: 'thread-title-generation',
|
|
574
|
-
})
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (thread.type === 'thread' && !thread.threadType) {
|
|
578
|
-
throw new ThreadTurnError('Core threads require a thread type.', 400)
|
|
579
|
-
}
|
|
580
|
-
const coreThreadProfile: CoreThreadProfile | null =
|
|
581
|
-
thread.type === 'thread' && thread.threadType ? getCoreThreadProfile(thread.threadType) : null
|
|
582
|
-
const defaultLeadAgentId = getLeadAgentId()
|
|
583
|
-
const visibleThreadAgentId =
|
|
584
|
-
params.agentIdOverride ??
|
|
585
|
-
(thread.type === 'default' ? thread.agentId : (coreThreadProfile?.config.agentId ?? defaultLeadAgentId))
|
|
586
|
-
const coreInstructionSections = coreThreadProfile ? [coreThreadProfile.instructions] : undefined
|
|
587
|
-
const assembledContext = await assembleThreadTurnContext({
|
|
588
|
-
thread,
|
|
589
|
-
threadRef,
|
|
590
|
-
orgRef,
|
|
591
|
-
userRef,
|
|
592
|
-
userName,
|
|
593
|
-
orgIdString,
|
|
594
|
-
userIdString,
|
|
595
|
-
messageText,
|
|
596
|
-
workspacePromise,
|
|
597
|
-
workspaceProvider,
|
|
598
|
-
turnHooks,
|
|
599
|
-
onStep: (step) => timer.step(step),
|
|
600
|
-
})
|
|
601
|
-
const {
|
|
602
|
-
workspace,
|
|
603
|
-
onboardingActive,
|
|
604
|
-
linearInstalled,
|
|
605
|
-
githubInstalled,
|
|
606
|
-
indexedRepoContext,
|
|
607
|
-
promptContext,
|
|
608
|
-
retrievedKnowledgeSection,
|
|
609
|
-
buildContextResult,
|
|
610
|
-
hookInstructionSections,
|
|
611
|
-
} = assembledContext
|
|
612
|
-
|
|
613
|
-
let memoryBlock = threadService.formatMemoryBlockForPrompt(threadRecord)
|
|
614
|
-
const executionPlanInstructionSectionCache = createExecutionPlanInstructionSectionCache({
|
|
615
|
-
disabled: false,
|
|
616
|
-
loadPlans: async () => {
|
|
617
|
-
const runs = await planRunService.getActiveRunRecords(threadRef)
|
|
618
|
-
return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run, { slim: true })))
|
|
619
|
-
},
|
|
620
|
-
})
|
|
621
|
-
const getExecutionPlanInstructionSections = async (): Promise<string[] | undefined> =>
|
|
622
|
-
await executionPlanInstructionSectionCache.getSections()
|
|
623
|
-
const invalidateExecutionPlanInstructionSections = () => {
|
|
624
|
-
executionPlanInstructionSectionCache.invalidate()
|
|
625
|
-
}
|
|
626
|
-
if (userMessage) {
|
|
627
|
-
const appliedHumanInput = await executionPlanService.applyHumanInputFromUserMessage({
|
|
628
|
-
threadId: threadRef,
|
|
629
|
-
message: userMessage,
|
|
630
|
-
respondedBy,
|
|
631
|
-
})
|
|
632
|
-
if (appliedHumanInput) {
|
|
633
|
-
invalidateExecutionPlanInstructionSections()
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
timer.step('execution-plan-input')
|
|
637
|
-
|
|
638
|
-
const preSeededMemoriesByAgent = new Map<string, string | undefined>()
|
|
639
|
-
const getPreSeededMemoriesSection = async (agentId: string): Promise<string | undefined> => {
|
|
640
|
-
if (preSeededMemoriesByAgent.has(agentId)) {
|
|
641
|
-
return preSeededMemoriesByAgent.get(agentId)
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
const preSeededMemories = await memoryService.getTopMemories({
|
|
645
|
-
orgId: orgIdString,
|
|
646
|
-
agentName: agentId,
|
|
647
|
-
limit: PRESEEDED_MEMORY_LOOKUP_LIMIT,
|
|
648
|
-
})
|
|
649
|
-
preSeededMemoriesByAgent.set(agentId, preSeededMemories)
|
|
650
|
-
return preSeededMemories
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const learnedSkillsByAgent = new Map<string, string | undefined>()
|
|
654
|
-
const getLearnedSkillsSection = async (agentId: string, queryText = messageText): Promise<string | undefined> => {
|
|
655
|
-
const cacheKey = `${agentId}::${queryText}`
|
|
656
|
-
if (learnedSkillsByAgent.has(cacheKey)) return learnedSkillsByAgent.get(cacheKey)
|
|
657
|
-
|
|
658
|
-
const section = await learnedSkillService
|
|
659
|
-
.retrieveForTurn({ orgId: orgIdString, agentId, query: queryText, limit: 3, minConfidence: 0.6 })
|
|
660
|
-
.catch((error) => {
|
|
661
|
-
aiLogger.warn`Failed to retrieve learned skills for ${agentId}: ${error}`
|
|
662
|
-
return undefined
|
|
663
|
-
})
|
|
664
|
-
learnedSkillsByAgent.set(cacheKey, section)
|
|
665
|
-
return section
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const persistedCompactionSummary =
|
|
669
|
-
persistedCompactionCursor && typeof threadRecord.compactionSummary === 'string'
|
|
670
|
-
? threadRecord.compactionSummary
|
|
671
|
-
: ''
|
|
672
|
-
const messagesForContext = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
673
|
-
let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedCompactionSummary, messagesForContext)
|
|
674
|
-
const referenceUserMessageId = referenceUserMessage?.id ?? ''
|
|
675
|
-
const listReadableUploads = (extraMessages: ChatMessage[] = []) =>
|
|
676
|
-
listReadableUploadsFromChatMessages({
|
|
677
|
-
messages: [...currentMessages, ...extraMessages],
|
|
678
|
-
orgId: orgRef,
|
|
679
|
-
userId: userRef,
|
|
680
|
-
})
|
|
681
|
-
const buildRunInputMessages = (extraMessages: ChatMessage[] = []): ChatMessage[] =>
|
|
682
|
-
buildModelInputMessagesWithUploadMetadata({
|
|
683
|
-
messages: [...currentMessages, ...extraMessages],
|
|
684
|
-
latestUserMessageId: referenceUserMessageId,
|
|
685
|
-
uploadMetadataText: buildReadableUploadMetadataText(listReadableUploads(extraMessages)),
|
|
686
|
-
})
|
|
687
|
-
const buildTurnToolParams = (toolParams: {
|
|
688
|
-
agentId: string
|
|
689
|
-
mode: 'direct' | 'fixedThreadMode' | 'threadMode'
|
|
690
|
-
memoryBlock: string
|
|
691
|
-
onAppendMemoryBlock: (value: string) => void
|
|
692
|
-
extraMessages?: ChatMessage[]
|
|
693
|
-
skills?: string[]
|
|
694
|
-
includeExecutionPlanTools: boolean
|
|
695
|
-
}) => ({
|
|
696
|
-
agentId: toolParams.agentId,
|
|
697
|
-
orgId: orgRef,
|
|
698
|
-
userId: userRef,
|
|
699
|
-
userName: userName ?? 'there',
|
|
700
|
-
threadId: threadRef,
|
|
701
|
-
orgIdString,
|
|
702
|
-
threadType: thread.type,
|
|
703
|
-
mode: toolParams.mode,
|
|
704
|
-
linearInstalled,
|
|
705
|
-
onboardingActive,
|
|
706
|
-
githubInstalled,
|
|
707
|
-
provideRepoTool: indexedRepoContext.provideRepoTool,
|
|
708
|
-
skills: toolParams.skills,
|
|
709
|
-
defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[toolParams.agentId],
|
|
710
|
-
memoryBlock: toolParams.memoryBlock,
|
|
711
|
-
onAppendMemoryBlock: toolParams.onAppendMemoryBlock,
|
|
712
|
-
availableUploads: listReadableUploads(toolParams.extraMessages),
|
|
713
|
-
includeExecutionPlanTools: toolParams.includeExecutionPlanTools,
|
|
714
|
-
onExecutionPlanChanged: invalidateExecutionPlanInstructionSections,
|
|
715
|
-
context: buildContextResult,
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
timer.step('preparation-complete')
|
|
719
|
-
|
|
720
|
-
return {
|
|
721
|
-
originalMessages,
|
|
722
|
-
run: async (writer?: UIMessageStreamWriter<ChatMessage>) => {
|
|
723
|
-
const executeRun = async (leaseAbortSignal?: AbortSignal): Promise<PreparedThreadTurnResult | void> => {
|
|
724
|
-
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(buildContextResult)
|
|
725
|
-
const runTimer = lotaDebugLogger.timer('run')
|
|
726
|
-
const serverRunId = Bun.randomUUIDv7()
|
|
727
|
-
const runAbortSignals = leaseAbortSignal ? [params.abortSignal, leaseAbortSignal] : [params.abortSignal]
|
|
728
|
-
const runAbort = createServerRunAbortController(
|
|
729
|
-
runAbortSignals.filter((signal): signal is AbortSignal => Boolean(signal)),
|
|
730
|
-
)
|
|
731
|
-
if (runAbort.signal.aborted) {
|
|
732
|
-
throw runAbort.signal.reason ?? new DOMException('The operation was aborted.', 'AbortError')
|
|
733
|
-
}
|
|
734
|
-
// Plan turns run without the chat lease — don't claim the active run slot.
|
|
735
|
-
if (params.kind !== 'planTurn') {
|
|
736
|
-
await threadService.setActiveTurn(threadRef, serverRunId, params.streamId ?? null)
|
|
737
|
-
chatRunRegistry.register(serverRunId, runAbort.controller)
|
|
738
|
-
}
|
|
739
|
-
runTimer.step('set-active-run+stream')
|
|
740
|
-
|
|
741
|
-
try {
|
|
742
|
-
const throwIfRunAborted = () => {
|
|
743
|
-
if (!runAbort.signal.aborted) return
|
|
744
|
-
throw runAbort.signal.reason ?? new DOMException('The operation was aborted.', 'AbortError')
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const buildAgentMetadataPatch = (agentId: string, agentName: string): NonNullable<MessageMetadata> => ({
|
|
748
|
-
agentId,
|
|
749
|
-
agentName,
|
|
750
|
-
semanticTerminationReason: 'none',
|
|
751
|
-
})
|
|
752
|
-
|
|
753
|
-
const createObserver = (agentId: string) => ({
|
|
754
|
-
run: <T>(fn: () => T | Promise<T>) => Promise.resolve(fn()),
|
|
755
|
-
recordError: (error: unknown) => {
|
|
756
|
-
aiLogger.error`Agent run failed (agent=${agentId}): ${error}`
|
|
757
|
-
},
|
|
758
|
-
recordAbort: (error: unknown) => {
|
|
759
|
-
aiLogger.info`Agent run aborted (agent=${agentId}): ${error instanceof Error ? error.message : String(error)}`
|
|
760
|
-
},
|
|
761
|
-
})
|
|
762
|
-
|
|
763
|
-
const commitAssistantResponse = async (
|
|
764
|
-
response: ChatMessage,
|
|
765
|
-
agentId: string,
|
|
766
|
-
agentName: string,
|
|
767
|
-
metadataPatch?: NonNullable<MessageMetadata>,
|
|
768
|
-
) => {
|
|
769
|
-
throwIfRunAborted()
|
|
770
|
-
|
|
771
|
-
const toCommittedAssistantMessage = (
|
|
772
|
-
message: ChatMessage,
|
|
773
|
-
resolvedAgentId: string,
|
|
774
|
-
resolvedAgentName: string,
|
|
775
|
-
patch?: NonNullable<MessageMetadata>,
|
|
776
|
-
) =>
|
|
777
|
-
withMessageCreatedAt(
|
|
778
|
-
{
|
|
779
|
-
...message,
|
|
780
|
-
metadata: {
|
|
781
|
-
...message.metadata,
|
|
782
|
-
...buildAgentMetadataPatch(resolvedAgentId, resolvedAgentName),
|
|
783
|
-
...patch,
|
|
784
|
-
},
|
|
785
|
-
},
|
|
786
|
-
toTimestamp(message.metadata?.createdAt) ?? Date.now(),
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
const committedConsultMessages = collectCompletedConsultTeamMessages({ responseMessage: response }).flatMap(
|
|
790
|
-
(consultMessage) => {
|
|
791
|
-
const consultAgentId = readOptionalString(consultMessage.metadata?.agentId)
|
|
792
|
-
const consultAgentName = readOptionalString(consultMessage.metadata?.agentName)
|
|
793
|
-
if (!consultAgentId || !consultAgentName) {
|
|
794
|
-
return []
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return [toCommittedAssistantMessage(consultMessage, consultAgentId, consultAgentName)]
|
|
798
|
-
},
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
const committed = toCommittedAssistantMessage(response, agentId, agentName, metadataPatch)
|
|
802
|
-
const messagesToPersist = [...committedConsultMessages, committed]
|
|
803
|
-
|
|
804
|
-
await threadMessageService.upsertMessages({ threadId: threadRef, messages: messagesToPersist })
|
|
805
|
-
for (const persistedMessage of messagesToPersist) {
|
|
806
|
-
currentMessages = upsertChatHistoryMessage(currentMessages, persistedMessage)
|
|
807
|
-
allAssistantMessages = upsertChatHistoryMessage(allAssistantMessages, persistedMessage)
|
|
808
|
-
}
|
|
809
|
-
throwIfRunAborted()
|
|
810
|
-
|
|
811
|
-
return committed
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
const streamCtx: StreamAgentResponseContext = {
|
|
815
|
-
turnHooks,
|
|
816
|
-
thread,
|
|
817
|
-
threadRef,
|
|
818
|
-
orgRef,
|
|
819
|
-
userRef,
|
|
820
|
-
userName,
|
|
821
|
-
onboardingActive,
|
|
822
|
-
linearInstalled,
|
|
823
|
-
githubInstalled,
|
|
824
|
-
buildContextResult,
|
|
825
|
-
getExecutionPlanInstructionSections,
|
|
826
|
-
getPreSeededMemoriesSection,
|
|
827
|
-
getLearnedSkillsSection,
|
|
828
|
-
promptContext,
|
|
829
|
-
retrievedKnowledgeSection,
|
|
830
|
-
memoryBlock,
|
|
831
|
-
hookInstructionSections,
|
|
832
|
-
runAbortSignal: runAbort.signal,
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const runVisibleAgent = async (runParams: {
|
|
836
|
-
agentId: string
|
|
837
|
-
mode: 'direct' | 'fixedThreadMode' | 'threadMode'
|
|
838
|
-
skills?: string[]
|
|
839
|
-
additionalInstructionSections?: string[]
|
|
840
|
-
extraMessages?: ChatMessage[]
|
|
841
|
-
extraTools?: ToolSet
|
|
842
|
-
filterTools?: (tools: ToolSet) => ToolSet
|
|
843
|
-
includeExecutionPlanTools?: boolean
|
|
844
|
-
metadataPatch?: NonNullable<MessageMetadata>
|
|
845
|
-
}): Promise<ChatMessage> => {
|
|
846
|
-
const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
|
|
847
|
-
let runMemoryBlock = memoryBlock
|
|
848
|
-
const includeExecutionPlanTools =
|
|
849
|
-
runParams.includeExecutionPlanTools ?? runParams.mode !== 'fixedThreadMode'
|
|
850
|
-
const rawTools: ToolSet = {
|
|
851
|
-
...((await buildAgentTools(
|
|
852
|
-
buildTurnToolParams({
|
|
853
|
-
agentId: runParams.agentId,
|
|
854
|
-
mode: runParams.mode,
|
|
855
|
-
skills: runParams.skills,
|
|
856
|
-
memoryBlock: runMemoryBlock,
|
|
857
|
-
onAppendMemoryBlock: (value: string) => {
|
|
858
|
-
runMemoryBlock = value
|
|
859
|
-
},
|
|
860
|
-
extraMessages: runParams.extraMessages,
|
|
861
|
-
includeExecutionPlanTools,
|
|
862
|
-
}),
|
|
863
|
-
)) as ToolSet),
|
|
864
|
-
...toolProviders,
|
|
865
|
-
...runParams.extraTools,
|
|
866
|
-
}
|
|
867
|
-
const tools = runParams.filterTools ? runParams.filterTools(rawTools) : rawTools
|
|
868
|
-
visibleTimer.step('build-agent-tools')
|
|
869
|
-
streamCtx.memoryBlock = memoryBlock
|
|
870
|
-
throwIfRunAborted()
|
|
871
|
-
const responseMessage = await streamAgentResponse(streamCtx, {
|
|
872
|
-
agentId: runParams.agentId,
|
|
873
|
-
mode: runParams.mode,
|
|
874
|
-
messages: buildRunInputMessages(runParams.extraMessages),
|
|
875
|
-
tools,
|
|
876
|
-
observer: createObserver(runParams.agentId),
|
|
877
|
-
skills: runParams.skills,
|
|
878
|
-
additionalInstructionSections: runParams.additionalInstructionSections,
|
|
879
|
-
includeExecutionPlanTools,
|
|
880
|
-
writer,
|
|
881
|
-
})
|
|
882
|
-
|
|
883
|
-
visibleTimer.step('stream-agent-response')
|
|
884
|
-
memoryBlock = runMemoryBlock
|
|
885
|
-
|
|
886
|
-
return commitAssistantResponse(
|
|
887
|
-
responseMessage,
|
|
888
|
-
runParams.agentId,
|
|
889
|
-
resolveRuntimeAgentDisplayName(agentIdentityOverrides, runParams.agentId),
|
|
890
|
-
runParams.metadataPatch,
|
|
891
|
-
)
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
if (params.kind === 'planTurn') {
|
|
895
|
-
const planTurn = params.planTurn
|
|
896
|
-
const submitPlanTurnNodeResultTool = createTool({
|
|
897
|
-
description: buildPlanTurnSubmitToolDescription(planTurn),
|
|
898
|
-
inputSchema: PlanNodeResultSubmissionSchema,
|
|
899
|
-
execute: async (result) =>
|
|
900
|
-
await executionPlanService.submitPlanTurnResult({
|
|
901
|
-
threadId: threadRef,
|
|
902
|
-
runId: planTurn.runId,
|
|
903
|
-
nodeId: planTurn.nodeId,
|
|
904
|
-
emittedBy: planTurn.nodeSpec.owner.ref,
|
|
905
|
-
input: result,
|
|
906
|
-
}),
|
|
907
|
-
})
|
|
908
|
-
|
|
909
|
-
await runVisibleAgent({
|
|
910
|
-
agentId: planTurn.nodeSpec.owner.ref,
|
|
911
|
-
mode: thread.type === 'default' ? 'direct' : 'threadMode',
|
|
912
|
-
additionalInstructionSections: buildPlanTurnInstructionSections(planTurn),
|
|
913
|
-
extraMessages: [buildPlanTurnPromptMessage(planTurn)],
|
|
914
|
-
includeExecutionPlanTools: false,
|
|
915
|
-
extraTools: { [SUBMIT_PLAN_TURN_RESULT_TOOL_NAME]: submitPlanTurnNodeResultTool },
|
|
916
|
-
filterTools: (tools) => applyPlanTurnToolPolicy(tools, planTurn.nodeSpec),
|
|
917
|
-
metadataPatch: { trigger: 'plan-turn', planRunId: planTurn.runId, planNodeId: planTurn.nodeId },
|
|
918
|
-
})
|
|
919
|
-
} else {
|
|
920
|
-
if (thread.type === 'default') {
|
|
921
|
-
if (!thread.agentId) {
|
|
922
|
-
throw new ThreadTurnError('Direct threads require an assigned agent.', 400)
|
|
923
|
-
}
|
|
924
|
-
await runVisibleAgent({ agentId: thread.agentId, mode: 'direct' })
|
|
925
|
-
} else {
|
|
926
|
-
// Multi-agent orchestration for group threads
|
|
927
|
-
const wsMembers = (thread as { members?: string[] }).members ?? []
|
|
928
|
-
const members = wsMembers.length > 0 ? wsMembers : [...agentRoster]
|
|
929
|
-
const fallbackAgentId = coreThreadProfile?.config.agentId ?? defaultLeadAgentId
|
|
930
|
-
throwIfRunAborted()
|
|
931
|
-
writeMultiAgentEvent(writer, { phase: 'routing', note: 'Routing this turn to the right agent.' })
|
|
932
|
-
|
|
933
|
-
const recentContext = currentMessages
|
|
934
|
-
.slice(-6)
|
|
935
|
-
.map((m) => `${m.role}: ${extractMessageText(m).slice(0, 200)}`)
|
|
936
|
-
.join('\n')
|
|
937
|
-
|
|
938
|
-
const triageResult = await triageThreadMessage({
|
|
939
|
-
threadTitle: thread.title,
|
|
940
|
-
members,
|
|
941
|
-
messageText,
|
|
942
|
-
recentContext,
|
|
943
|
-
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
944
|
-
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
945
|
-
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
946
|
-
})
|
|
947
|
-
throwIfRunAborted()
|
|
948
|
-
|
|
949
|
-
const runGroupAgent = async (agentId: string, options?: { routingContext?: string }) => {
|
|
950
|
-
const additionalSections = [...(coreInstructionSections ?? []), ...hookInstructionSections]
|
|
951
|
-
if (options?.routingContext) {
|
|
952
|
-
additionalSections.push(`<routing-context>\n${options.routingContext}\n</routing-context>`)
|
|
953
|
-
}
|
|
954
|
-
// Multi-agent member protocol: be direct, focus on domain
|
|
955
|
-
additionalSections.push(
|
|
956
|
-
'<multi-agent-protocol>\nYou are responding as part of a multi-agent thread. Focus on your domain expertise. Be direct and concise — another agent may follow up on different aspects.\n</multi-agent-protocol>',
|
|
957
|
-
)
|
|
958
|
-
|
|
959
|
-
return await runVisibleAgent({
|
|
960
|
-
agentId,
|
|
961
|
-
mode: 'threadMode',
|
|
962
|
-
skills: coreThreadProfile?.skills ? [...coreThreadProfile.skills] : undefined,
|
|
963
|
-
additionalInstructionSections: additionalSections,
|
|
964
|
-
})
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
if (!triageResult) {
|
|
968
|
-
// No specialist match — fallback to owner (core) or chief (non-core), single visible turn.
|
|
969
|
-
await runGroupAgent(fallbackAgentId)
|
|
970
|
-
throwIfRunAborted()
|
|
971
|
-
writeMultiAgentEvent(writer, { phase: 'complete' })
|
|
972
|
-
} else {
|
|
973
|
-
const respondedAgents: string[] = []
|
|
974
|
-
let lastResponse = await runGroupAgent(triageResult.agentId, {
|
|
975
|
-
routingContext: triageResult.routingContext,
|
|
976
|
-
})
|
|
977
|
-
respondedAgents.push(triageResult.agentId)
|
|
978
|
-
throwIfRunAborted()
|
|
979
|
-
|
|
980
|
-
// Follow-up specialists stream visibly in order so the user can
|
|
981
|
-
// watch each specialist reply instead of waiting for a persisted refresh.
|
|
982
|
-
while (respondedAgents.length < 3) {
|
|
983
|
-
const lastResponseText = extractMessageText(lastResponse).slice(0, 500)
|
|
984
|
-
const checkResult = await checkForNextAgent({
|
|
985
|
-
threadTitle: thread.title,
|
|
986
|
-
members,
|
|
987
|
-
messageText,
|
|
988
|
-
respondedAgents,
|
|
989
|
-
lastResponseSummary: lastResponseText,
|
|
990
|
-
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
991
|
-
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
992
|
-
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
993
|
-
})
|
|
994
|
-
throwIfRunAborted()
|
|
995
|
-
|
|
996
|
-
if (checkResult.done || !checkResult.agentId) break
|
|
997
|
-
|
|
998
|
-
writeMultiAgentEvent(writer, {
|
|
999
|
-
phase: 'waiting-for-agent',
|
|
1000
|
-
agentId: checkResult.agentId,
|
|
1001
|
-
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
1002
|
-
note: checkResult.routingContext ?? undefined,
|
|
1003
|
-
})
|
|
1004
|
-
|
|
1005
|
-
// Insert hidden bridge message between agent turns
|
|
1006
|
-
const bridgeMessage: ChatMessage = {
|
|
1007
|
-
id: Bun.randomUUIDv7(),
|
|
1008
|
-
role: 'user',
|
|
1009
|
-
parts: [
|
|
1010
|
-
{
|
|
1011
|
-
type: 'text',
|
|
1012
|
-
text: checkResult.routingContext ?? 'Please also provide your perspective on this topic.',
|
|
1013
|
-
},
|
|
1014
|
-
],
|
|
1015
|
-
metadata: { hidden: true, createdAt: Date.now() } as MessageMetadata,
|
|
1016
|
-
}
|
|
1017
|
-
throwIfRunAborted()
|
|
1018
|
-
await threadMessageService.upsertMessages({ threadId: threadRef, messages: [bridgeMessage] })
|
|
1019
|
-
currentMessages = upsertChatHistoryMessage(currentMessages, bridgeMessage)
|
|
1020
|
-
throwIfRunAborted()
|
|
1021
|
-
|
|
1022
|
-
lastResponse = await runGroupAgent(checkResult.agentId, {
|
|
1023
|
-
routingContext: checkResult.routingContext ?? undefined,
|
|
1024
|
-
})
|
|
1025
|
-
respondedAgents.push(checkResult.agentId)
|
|
1026
|
-
throwIfRunAborted()
|
|
1027
|
-
writeMultiAgentEvent(writer, {
|
|
1028
|
-
phase: 'agent-message-persisted',
|
|
1029
|
-
agentId: checkResult.agentId,
|
|
1030
|
-
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
1031
|
-
messageId: lastResponse.id,
|
|
1032
|
-
})
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
throwIfRunAborted()
|
|
1036
|
-
writeMultiAgentEvent(writer, { phase: 'complete' })
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
} finally {
|
|
1041
|
-
try {
|
|
1042
|
-
const latestThreadRecord = await threadService.getById(threadRef)
|
|
1043
|
-
|
|
1044
|
-
await finalizeTurnRun({
|
|
1045
|
-
serverRunId,
|
|
1046
|
-
getEntity: async () => latestThreadRecord,
|
|
1047
|
-
getUncompactedMessages: (cursor) => threadMessageService.listMessagesAfterCursor(threadRef, cursor),
|
|
1048
|
-
assessCompaction: (summaryText, messages) =>
|
|
1049
|
-
contextCompactionRuntime.shouldCompactHistory({
|
|
1050
|
-
summaryText,
|
|
1051
|
-
liveMessages: messages,
|
|
1052
|
-
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
1053
|
-
}),
|
|
1054
|
-
enqueueCompaction: async () => {
|
|
1055
|
-
await enqueueContextCompaction({
|
|
1056
|
-
domain: 'thread',
|
|
1057
|
-
entityId: threadIdString,
|
|
1058
|
-
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
1059
|
-
})
|
|
1060
|
-
},
|
|
1061
|
-
unregisterRun: (runId) => chatRunRegistry.unregister(runId),
|
|
1062
|
-
clearActiveRunId: async (runId) => {
|
|
1063
|
-
const activeStreamId = await threadService.getActiveStreamId(threadRef)
|
|
1064
|
-
await threadService.clearActiveTurn(threadRef, { runId, streamId: activeStreamId })
|
|
1065
|
-
},
|
|
1066
|
-
disposeAbort: () => runAbort.dispose(),
|
|
1067
|
-
activeStreamId: params.streamId,
|
|
1068
|
-
clearActiveStreamId: async (streamId) => {
|
|
1069
|
-
const activeRunId = await threadService.getActiveRunId(threadRef)
|
|
1070
|
-
if (!activeRunId) return
|
|
1071
|
-
await threadService.clearActiveTurn(threadRef, { runId: activeRunId, streamId })
|
|
1072
|
-
},
|
|
1073
|
-
})
|
|
1074
|
-
|
|
1075
|
-
if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
|
|
1076
|
-
await runPostTurnSideEffects({
|
|
1077
|
-
thread,
|
|
1078
|
-
threadRef,
|
|
1079
|
-
orgRef,
|
|
1080
|
-
userRef,
|
|
1081
|
-
userName,
|
|
1082
|
-
orgIdString,
|
|
1083
|
-
threadIdString,
|
|
1084
|
-
onboardingActive,
|
|
1085
|
-
workspace,
|
|
1086
|
-
allAssistantMessages,
|
|
1087
|
-
referenceUserMessage,
|
|
1088
|
-
referenceUserMessageId,
|
|
1089
|
-
loadRecentHistory,
|
|
1090
|
-
listReadableUploads: () => listReadableUploads(),
|
|
1091
|
-
memoryBlock,
|
|
1092
|
-
visibleThreadAgentId,
|
|
1093
|
-
defaultLeadAgentId,
|
|
1094
|
-
latestThreadRecord,
|
|
1095
|
-
isUserTurn: params.kind === 'userTurn',
|
|
1096
|
-
agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
|
|
1097
|
-
})
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
if (allAssistantMessages.length > 0 && params.kind !== 'planTurn') {
|
|
1101
|
-
await turnHooks.afterTurn?.({
|
|
1102
|
-
thread,
|
|
1103
|
-
threadRef,
|
|
1104
|
-
orgRef,
|
|
1105
|
-
userRef,
|
|
1106
|
-
userName,
|
|
1107
|
-
onboardingActive,
|
|
1108
|
-
referenceUserMessage,
|
|
1109
|
-
assistantMessages: allAssistantMessages,
|
|
1110
|
-
latestThreadRecord,
|
|
1111
|
-
context: buildContextResult,
|
|
1112
|
-
})
|
|
1113
|
-
}
|
|
1114
|
-
} catch (postRunError) {
|
|
1115
|
-
aiLogger.error`Thread post-run cleanup failed: ${postRunError}`
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// Plan turns run without the chat lease so they never block user messages.
|
|
1121
|
-
// The heartbeat lock already prevents duplicate plan turns on the same node.
|
|
1122
|
-
if (params.kind === 'planTurn') {
|
|
1123
|
-
const runResult = await executeRun()
|
|
1124
|
-
if (runResult) {
|
|
1125
|
-
return runResult
|
|
1126
|
-
}
|
|
1127
|
-
return { inputMessageId: referenceUserMessage?.id, assistantMessages: [...allAssistantMessages] }
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
try {
|
|
1131
|
-
return await threadService.withActiveRunLease(threadRef, async (leaseAbortSignal) => {
|
|
1132
|
-
const runResult = await executeRun(leaseAbortSignal)
|
|
1133
|
-
if (runResult) {
|
|
1134
|
-
return runResult
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
return { inputMessageId: referenceUserMessage?.id, assistantMessages: [...allAssistantMessages] }
|
|
1138
|
-
})
|
|
1139
|
-
} catch (error) {
|
|
1140
|
-
if (error instanceof ActiveThreadRunConflictError) {
|
|
1141
|
-
throw new ThreadTurnError(error.message, 409)
|
|
1142
|
-
}
|
|
1143
|
-
throw error
|
|
1144
|
-
}
|
|
1145
|
-
},
|
|
1146
|
-
}
|
|
1147
|
-
}
|