@lota-sdk/core 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +1 -0
- package/src/bifrost/bifrost.ts +12 -7
- package/src/config/agent-defaults.ts +1 -1
- package/src/config/logger.ts +7 -9
- package/src/{runtime.ts → create-runtime.ts} +6 -6
- package/src/db/cursor-pagination.ts +1 -1
- package/src/db/memory-store.ts +10 -6
- package/src/db/memory.ts +6 -4
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +45 -51
- package/src/db/startup.ts +3 -3
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +4 -8
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/memory-consolidation.queue.ts +7 -8
- package/src/queues/post-chat-memory.queue.ts +2 -6
- package/src/queues/recent-activity-title-refinement.queue.ts +2 -6
- package/src/queues/regular-chat-memory-digest.queue.ts +4 -7
- package/src/queues/skill-extraction.queue.ts +4 -7
- package/src/queues/workstream-title-generation.queue.ts +2 -6
- package/src/redis/connection.ts +6 -3
- package/src/redis/index.ts +1 -0
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +6 -4
- package/src/runtime/context-compaction.ts +19 -38
- package/src/runtime/execution-plan.ts +2 -2
- package/src/runtime/helper-model.ts +3 -1
- package/src/runtime/index.ts +12 -1
- package/src/runtime/memory-block.ts +3 -2
- package/src/runtime/memory-pipeline.ts +24 -5
- package/src/runtime/plugin-types.ts +1 -1
- package/src/runtime/runtime-extensions.ts +89 -13
- package/src/runtime/title-helpers.ts +11 -2
- package/src/runtime/workstream-chat-helpers.ts +5 -6
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/attachment.service.ts +1 -1
- package/src/services/context-compaction.service.ts +3 -3
- package/src/services/document-chunk.service.ts +37 -32
- package/src/services/execution-plan.service.ts +2 -0
- package/src/services/learned-skill.service.ts +6 -10
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -8
- package/src/services/memory.service.ts +21 -18
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-executor.service.ts +2 -18
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-validator.service.ts +3 -18
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +6 -12
- package/src/services/workstream-message.service.ts +26 -16
- package/src/services/workstream-title.service.ts +1 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +401 -314
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +22 -10
- package/src/services/workstream.types.ts +7 -16
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +1 -4
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +3 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +6 -2
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -2
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +9 -5
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +10 -2
- package/src/utils/string.ts +14 -0
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +9 -103
- package/src/workers/skill-extraction.runner.ts +7 -101
- package/src/workers/utils/file-section-chunker.ts +5 -3
- package/src/workers/utils/workstream-message-query.ts +106 -0
- package/src/workers/worker-utils.ts +4 -0
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts}
RENAMED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
CONSULT_SPECIALIST_TOOL_NAME,
|
|
4
4
|
CONSULT_TEAM_TOOL_NAME,
|
|
5
5
|
ConsultSpecialistArgsSchema,
|
|
6
|
-
|
|
6
|
+
dataPartsSchemas,
|
|
7
7
|
messageMetadataSchema,
|
|
8
8
|
toTimestamp,
|
|
9
9
|
withMessageCreatedAt,
|
|
@@ -13,6 +13,7 @@ import { convertToModelMessages, readUIMessageStream, stepCountIs, tool as creat
|
|
|
13
13
|
import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
|
|
14
14
|
import type { z } from 'zod'
|
|
15
15
|
|
|
16
|
+
import type { CoreWorkstreamProfile } from '../config/agent-defaults'
|
|
16
17
|
import {
|
|
17
18
|
agentDisplayNames,
|
|
18
19
|
buildAgentTools,
|
|
@@ -45,7 +46,7 @@ import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataT
|
|
|
45
46
|
import { hasMessageContent } from '../runtime/chat-message'
|
|
46
47
|
import { waitForCompactionIfNeeded } from '../runtime/chat-run-orchestration'
|
|
47
48
|
import { parseWorkstreamState } from '../runtime/context-compaction'
|
|
48
|
-
import {
|
|
49
|
+
import { CONTEXT_WINDOW_TOKENS } from '../runtime/context-compaction-constants'
|
|
49
50
|
import { createExecutionPlanInstructionSectionCache } from '../runtime/execution-plan'
|
|
50
51
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
51
52
|
import {
|
|
@@ -81,7 +82,7 @@ import { toIsoDateTimeString } from '../utils/date-time'
|
|
|
81
82
|
import { AppError } from '../utils/errors'
|
|
82
83
|
import { attachmentService } from './attachment.service'
|
|
83
84
|
import { listReadableUploadsFromChatMessages } from './chat-attachments.service'
|
|
84
|
-
import { contextCompactionRuntime } from './context-compaction-runtime'
|
|
85
|
+
import { contextCompactionRuntime } from './context-compaction-runtime.singleton'
|
|
85
86
|
import { executionPlanService } from './execution-plan.service'
|
|
86
87
|
import { learnedSkillService } from './learned-skill.service'
|
|
87
88
|
import { memoryService } from './memory.service'
|
|
@@ -231,7 +232,7 @@ export interface WorkstreamTurnParams {
|
|
|
231
232
|
userRef: RecordIdRef
|
|
232
233
|
userName?: string | null
|
|
233
234
|
inputMessage: ChatMessage
|
|
234
|
-
|
|
235
|
+
skipInputMessagePersistence?: boolean
|
|
235
236
|
abortSignal?: AbortSignal
|
|
236
237
|
streamId?: string
|
|
237
238
|
}
|
|
@@ -256,7 +257,7 @@ type WorkstreamRunCoreParams = {
|
|
|
256
257
|
abortSignal?: AbortSignal
|
|
257
258
|
streamId?: string
|
|
258
259
|
} & (
|
|
259
|
-
| { kind: 'userTurn'; inputMessage: ChatMessage;
|
|
260
|
+
| { kind: 'userTurn'; inputMessage: ChatMessage; skipInputMessagePersistence?: boolean }
|
|
260
261
|
| { kind: 'approvalContinuation'; approvalMessages: ChatMessage[] }
|
|
261
262
|
| { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
|
|
262
263
|
)
|
|
@@ -300,6 +301,338 @@ function buildRecentActivityChatSystemTitle(params: {
|
|
|
300
301
|
return params.workstream.title.trim() || 'Workstream update'
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
interface StreamAgentResponseContext {
|
|
305
|
+
turnHooks: ReturnType<typeof getTurnHooks>
|
|
306
|
+
workstream: NormalizedWorkstream
|
|
307
|
+
workstreamRef: RecordIdRef
|
|
308
|
+
orgRef: RecordIdRef
|
|
309
|
+
userRef: RecordIdRef
|
|
310
|
+
userName?: string | null
|
|
311
|
+
onboardingActive: boolean
|
|
312
|
+
linearInstalled: boolean
|
|
313
|
+
githubInstalled: boolean
|
|
314
|
+
reasoningProfileName: string
|
|
315
|
+
buildContextResult: Record<string, unknown> | null
|
|
316
|
+
getExecutionPlanInstructionSections: () => Promise<string[] | undefined>
|
|
317
|
+
getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
|
|
318
|
+
getWorkstreamStateSection: () => Promise<string | undefined>
|
|
319
|
+
getLearnedSkillsSection: (agentId: string) => Promise<string | undefined>
|
|
320
|
+
promptContext: { systemWorkspaceDetails?: string }
|
|
321
|
+
retrievedKnowledgeSection: string | undefined
|
|
322
|
+
memoryBlock: string
|
|
323
|
+
hookInstructionSections: string[]
|
|
324
|
+
runAbortSignal: AbortSignal
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
interface StreamAgentResponseParams {
|
|
328
|
+
agentId: string
|
|
329
|
+
mode: 'direct' | 'fixedWorkstreamMode' | 'workstreamMode'
|
|
330
|
+
messages: ChatMessage[]
|
|
331
|
+
tools: ToolSet
|
|
332
|
+
observer: {
|
|
333
|
+
run: <T>(fn: () => T | Promise<T>) => Promise<T>
|
|
334
|
+
recordError: (error: unknown) => void
|
|
335
|
+
recordAbort: (error: unknown) => void
|
|
336
|
+
}
|
|
337
|
+
skills?: string[]
|
|
338
|
+
additionalInstructionSections?: string[]
|
|
339
|
+
writer?: UIMessageStreamWriter<ChatMessage>
|
|
340
|
+
stopWhen?: StopCondition<ToolSet> | Array<StopCondition<ToolSet>>
|
|
341
|
+
prepareStep?: PrepareStepFunction<ToolSet>
|
|
342
|
+
abortSignal?: AbortSignal
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function streamAgentResponse(
|
|
346
|
+
ctx: StreamAgentResponseContext,
|
|
347
|
+
streamParams: StreamAgentResponseParams,
|
|
348
|
+
): Promise<ChatMessage> {
|
|
349
|
+
const agentTimer = lotaDebugLogger.timer(`agent:${streamParams.agentId}`)
|
|
350
|
+
const executionPlanInstructionSections = await ctx.getExecutionPlanInstructionSections()
|
|
351
|
+
agentTimer.step('get-execution-plan')
|
|
352
|
+
const agentResolution = asRecord(
|
|
353
|
+
await ctx.turnHooks.resolveAgent?.({
|
|
354
|
+
agentId: streamParams.agentId,
|
|
355
|
+
mode: streamParams.mode,
|
|
356
|
+
workstream: ctx.workstream,
|
|
357
|
+
workstreamRef: ctx.workstreamRef,
|
|
358
|
+
orgRef: ctx.orgRef,
|
|
359
|
+
userRef: ctx.userRef,
|
|
360
|
+
userName: ctx.userName,
|
|
361
|
+
onboardingActive: ctx.onboardingActive,
|
|
362
|
+
linearInstalled: ctx.linearInstalled,
|
|
363
|
+
githubInstalled: ctx.githubInstalled,
|
|
364
|
+
reasoningProfile: ctx.reasoningProfileName,
|
|
365
|
+
skills: streamParams.skills,
|
|
366
|
+
additionalInstructionSections: streamParams.additionalInstructionSections,
|
|
367
|
+
context: ctx.buildContextResult,
|
|
368
|
+
}),
|
|
369
|
+
)
|
|
370
|
+
agentTimer.step('hook:resolveAgent')
|
|
371
|
+
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
|
|
372
|
+
const [preSeededMemoriesSection, workstreamStateSection, learnedSkillsSection] = await Promise.all([
|
|
373
|
+
ctx.getPreSeededMemoriesSection(resolvedAgentId),
|
|
374
|
+
ctx.getWorkstreamStateSection(),
|
|
375
|
+
ctx.getLearnedSkillsSection(resolvedAgentId),
|
|
376
|
+
])
|
|
377
|
+
agentTimer.step('parallel-fetch(memories+state+skills)')
|
|
378
|
+
const config = getAgentRuntimeConfig({
|
|
379
|
+
agentId: resolvedAgentId,
|
|
380
|
+
workstreamMode: ctx.workstream.mode,
|
|
381
|
+
mode: streamParams.mode,
|
|
382
|
+
skills: streamParams.skills,
|
|
383
|
+
onboardingActive: ctx.onboardingActive,
|
|
384
|
+
linearInstalled: ctx.linearInstalled,
|
|
385
|
+
reasoningProfile: ctx.reasoningProfileName,
|
|
386
|
+
systemWorkspaceDetails: ctx.promptContext.systemWorkspaceDetails,
|
|
387
|
+
preSeededMemoriesSection,
|
|
388
|
+
retrievedKnowledgeSection: ctx.retrievedKnowledgeSection,
|
|
389
|
+
workstreamMemoryBlock: ctx.memoryBlock,
|
|
390
|
+
workstreamStateSection,
|
|
391
|
+
learnedSkillsSection,
|
|
392
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
393
|
+
executionPlanInstructionSections,
|
|
394
|
+
streamParams.additionalInstructionSections,
|
|
395
|
+
ctx.hookInstructionSections,
|
|
396
|
+
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
397
|
+
optionalInstructionSection(agentResolution?.extraInstructions),
|
|
398
|
+
),
|
|
399
|
+
context: ctx.buildContextResult,
|
|
400
|
+
}) as AgentRuntimeConfig
|
|
401
|
+
agentTimer.step('build-agent-config')
|
|
402
|
+
const modelMessages = await convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true })
|
|
403
|
+
agentTimer.step('convert-model-messages')
|
|
404
|
+
const agent = (createAgent as unknown as AgentFactory)[config.id as string]({
|
|
405
|
+
mode: streamParams.mode,
|
|
406
|
+
tools: streamParams.tools,
|
|
407
|
+
extraInstructions: config.extraInstructions,
|
|
408
|
+
stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
|
|
409
|
+
streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
|
|
410
|
+
prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
|
|
411
|
+
})
|
|
412
|
+
const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
|
|
413
|
+
agentTimer.step('agent-construction')
|
|
414
|
+
|
|
415
|
+
let result: unknown
|
|
416
|
+
try {
|
|
417
|
+
result = await streamParams.observer.run(() =>
|
|
418
|
+
agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
|
|
419
|
+
)
|
|
420
|
+
agentTimer.step('agent.stream()-resolved')
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (agentAbortSignal.aborted) {
|
|
423
|
+
streamParams.observer.recordAbort(error)
|
|
424
|
+
} else {
|
|
425
|
+
streamParams.observer.recordError(error)
|
|
426
|
+
}
|
|
427
|
+
throw error
|
|
428
|
+
}
|
|
429
|
+
if (!hasUIMessageStream(result)) {
|
|
430
|
+
throw new Error(`Agent run for ${resolvedAgentId} did not expose a UI message stream.`)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let responseMessage: ChatMessage | null = null
|
|
434
|
+
let resolveFinishedStream!: () => void
|
|
435
|
+
const finishedStream = new Promise<void>((resolve) => {
|
|
436
|
+
resolveFinishedStream = resolve
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const uiStream = result.toUIMessageStream({
|
|
440
|
+
generateMessageId: () => Bun.randomUUIDv7(),
|
|
441
|
+
originalMessages: streamParams.messages,
|
|
442
|
+
sendReasoning: true,
|
|
443
|
+
sendSources: true,
|
|
444
|
+
messageMetadata: createAgentMessageMetadata({ agentId: resolvedAgentId, agentName: config.displayName as string }),
|
|
445
|
+
onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
|
|
446
|
+
responseMessage = withMessageCreatedAt(finishedResponseMessage)
|
|
447
|
+
resolveFinishedStream()
|
|
448
|
+
},
|
|
449
|
+
}) as ReadableStream<ChatStreamChunk>
|
|
450
|
+
const reader = uiStream.getReader()
|
|
451
|
+
let firstChunkLogged = false
|
|
452
|
+
try {
|
|
453
|
+
for (;;) {
|
|
454
|
+
const { done, value } = await reader.read()
|
|
455
|
+
if (done) break
|
|
456
|
+
if (!firstChunkLogged) {
|
|
457
|
+
agentTimer.step('first-stream-chunk')
|
|
458
|
+
firstChunkLogged = true
|
|
459
|
+
}
|
|
460
|
+
if (streamParams.writer) {
|
|
461
|
+
streamParams.writer.write(value)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
reader.releaseLock()
|
|
466
|
+
}
|
|
467
|
+
agentTimer.step('stream-complete')
|
|
468
|
+
|
|
469
|
+
await finishedStream
|
|
470
|
+
// responseMessage is set inside the stream callback — linter cannot track cross-callback assignment
|
|
471
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
472
|
+
if (responseMessage === null) {
|
|
473
|
+
throw new Error(`Agent run for ${resolvedAgentId} did not produce a response message.`)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const toolError of collectToolOutputErrors({ responseMessage: responseMessage })) {
|
|
477
|
+
aiLogger.warn`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return responseMessage
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
interface PostTurnSideEffectsParams {
|
|
484
|
+
workstream: NormalizedWorkstream
|
|
485
|
+
workstreamRef: RecordIdRef
|
|
486
|
+
orgRef: RecordIdRef
|
|
487
|
+
userRef: RecordIdRef
|
|
488
|
+
userName?: string | null
|
|
489
|
+
orgIdString: string
|
|
490
|
+
workstreamIdString: string
|
|
491
|
+
onboardingActive: boolean
|
|
492
|
+
workspace: unknown
|
|
493
|
+
allAssistantMessages: ChatMessage[]
|
|
494
|
+
referenceUserMessage: ChatMessage | undefined
|
|
495
|
+
referenceUserMessageId: string
|
|
496
|
+
recentHistory: ChatMessage[]
|
|
497
|
+
listReadableUploads: () => ReturnType<typeof listReadableUploadsFromChatMessages>
|
|
498
|
+
memoryBlock: string
|
|
499
|
+
visibleWorkstreamAgentId: string | null | undefined
|
|
500
|
+
defaultLeadAgentId: string
|
|
501
|
+
latestWorkstreamRecord: WorkstreamRecord
|
|
502
|
+
latestPersistedState: WorkstreamState | null
|
|
503
|
+
turnHooks: ReturnType<typeof getTurnHooks>
|
|
504
|
+
buildContextResult: Record<string, unknown> | null
|
|
505
|
+
isUserTurn: boolean
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function runPostTurnSideEffects(params: PostTurnSideEffectsParams): Promise<void> {
|
|
509
|
+
const turnCount = await workstreamService.incrementTurnCount(params.workstreamRef)
|
|
510
|
+
const agentMessages = buildAgentHistoryMessages(params.allAssistantMessages)
|
|
511
|
+
const historyMessagesForMemory = appendPersistedWorkstreamContextToHistoryMessages(
|
|
512
|
+
toHistoryMessages(params.recentHistory),
|
|
513
|
+
{ compactionSummary: params.latestWorkstreamRecord.compactionSummary, persistedState: params.latestPersistedState },
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
const userMessageText = params.referenceUserMessage ? extractMessageText(params.referenceUserMessage).trim() : ''
|
|
517
|
+
const readableUploads = params.listReadableUploads()
|
|
518
|
+
const attachmentMetadataContext = buildReadableUploadMetadataContext(readableUploads)
|
|
519
|
+
const hasAttachmentContext = Boolean(attachmentMetadataContext)
|
|
520
|
+
const shouldExtractMemory = params.onboardingActive
|
|
521
|
+
? shouldEnqueueOnboardingPostChatMemory({
|
|
522
|
+
onboardingActive: params.onboardingActive,
|
|
523
|
+
userMessageText,
|
|
524
|
+
hasAttachmentContext,
|
|
525
|
+
agentMessageCount: agentMessages.length,
|
|
526
|
+
})
|
|
527
|
+
: shouldEnqueueMemoryExtraction({ onboardingActive: params.onboardingActive, turnCount }) &&
|
|
528
|
+
userMessageText.length > 0
|
|
529
|
+
|
|
530
|
+
if (shouldExtractMemory) {
|
|
531
|
+
const memoryUserMessage = userMessageText || 'User uploaded attachment(s).'
|
|
532
|
+
await safeEnqueue(
|
|
533
|
+
() =>
|
|
534
|
+
enqueuePostChatMemory({
|
|
535
|
+
orgId: params.orgIdString,
|
|
536
|
+
workstreamId: params.workstreamIdString,
|
|
537
|
+
sourceId: params.referenceUserMessageId,
|
|
538
|
+
onboardStatus: readOptionalString((params.workspace as { onboardStatus?: unknown }).onboardStatus),
|
|
539
|
+
userMessage: memoryUserMessage,
|
|
540
|
+
historyMessages: historyMessagesForMemory,
|
|
541
|
+
agentMessages,
|
|
542
|
+
memoryBlock: params.memoryBlock.trim() ? params.memoryBlock : undefined,
|
|
543
|
+
attachmentContext: attachmentMetadataContext,
|
|
544
|
+
}),
|
|
545
|
+
{ operationName: 'post-chat memory extraction enqueue' },
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (params.isUserTurn && params.referenceUserMessage) {
|
|
550
|
+
const conversationSummary = buildConversationSummary({
|
|
551
|
+
userMessageText,
|
|
552
|
+
assistantMessages: params.allAssistantMessages,
|
|
553
|
+
})
|
|
554
|
+
if (conversationSummary) {
|
|
555
|
+
const effectiveAgentId = params.visibleWorkstreamAgentId ?? params.defaultLeadAgentId
|
|
556
|
+
const recentActivityResult = await recentActivityService.recordEvent({
|
|
557
|
+
orgId: params.orgRef,
|
|
558
|
+
userId: params.userRef,
|
|
559
|
+
source: 'system',
|
|
560
|
+
event: {
|
|
561
|
+
sourceEventId: `chat-turn:${params.referenceUserMessageId}`,
|
|
562
|
+
kind: 'chat.turn.completed',
|
|
563
|
+
targetKind: 'workstream',
|
|
564
|
+
targetId: params.workstreamIdString,
|
|
565
|
+
mergeKey: `workstream:${params.workstreamIdString}`,
|
|
566
|
+
title: buildRecentActivityChatSystemTitle({
|
|
567
|
+
workstream: params.workstream,
|
|
568
|
+
visibleAgentId: effectiveAgentId,
|
|
569
|
+
}),
|
|
570
|
+
sourceLabel: agentDisplayNames[effectiveAgentId],
|
|
571
|
+
deepLink: buildRecentActivityChatDeepLink({
|
|
572
|
+
workstream: params.workstream,
|
|
573
|
+
workstreamId: params.workstreamIdString,
|
|
574
|
+
visibleAgentId: effectiveAgentId,
|
|
575
|
+
}),
|
|
576
|
+
metadata: {
|
|
577
|
+
agentId: effectiveAgentId,
|
|
578
|
+
agentName: agentDisplayNames[effectiveAgentId],
|
|
579
|
+
workstreamId: params.workstreamIdString,
|
|
580
|
+
workstreamTitle: params.latestWorkstreamRecord.title ?? params.workstream.title,
|
|
581
|
+
workstreamMode: params.workstream.mode,
|
|
582
|
+
...(params.workstream.coreType ? { coreType: params.workstream.coreType } : {}),
|
|
583
|
+
userMessageText,
|
|
584
|
+
assistantSummary: conversationSummary,
|
|
585
|
+
messageId: params.referenceUserMessageId,
|
|
586
|
+
},
|
|
587
|
+
occurredAt: toIsoDateTimeString(params.referenceUserMessage.metadata?.createdAt ?? Date.now()),
|
|
588
|
+
},
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
await safeEnqueue(
|
|
592
|
+
async () => {
|
|
593
|
+
const enqueuePostChatOrgAction = getRuntimeAdapters().queues?.enqueuePostChatOrgAction
|
|
594
|
+
if (!enqueuePostChatOrgAction) {
|
|
595
|
+
return
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
await enqueuePostChatOrgAction({
|
|
599
|
+
orgId: params.orgIdString,
|
|
600
|
+
workstreamId: params.workstreamIdString,
|
|
601
|
+
sourceId: params.referenceUserMessageId,
|
|
602
|
+
sourceCreatedAt: params.referenceUserMessage?.metadata?.createdAt ?? Date.now(),
|
|
603
|
+
conversationSummary,
|
|
604
|
+
})
|
|
605
|
+
},
|
|
606
|
+
{ operationName: 'post-chat org action enqueue' },
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if (recentActivityService.isMeaningfulRefinementCandidate(recentActivityResult.item)) {
|
|
610
|
+
await safeEnqueue(() => enqueueRecentActivityTitleRefinement({ activityId: recentActivityResult.item.id }), {
|
|
611
|
+
operationName: 'recent activity title refinement enqueue',
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (shouldEnqueueRegularDigestForWorkstream({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
618
|
+
await safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: params.orgIdString }), {
|
|
619
|
+
operationName: 'regular chat memory digest enqueue',
|
|
620
|
+
})
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (shouldEnqueueSkillExtraction({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
624
|
+
await safeEnqueue(() => enqueueSkillExtraction({ orgId: params.orgIdString }), {
|
|
625
|
+
operationName: 'skill extraction enqueue',
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (shouldEnqueueMemoryConsolidation({ onboardingActive: params.onboardingActive, turnCount })) {
|
|
630
|
+
await safeEnqueue(() => enqueueMemoryConsolidation({ scopeId: params.orgIdString }), {
|
|
631
|
+
operationName: 'memory consolidation enqueue',
|
|
632
|
+
})
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
303
636
|
export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams): Promise<PreparedWorkstreamTurn> {
|
|
304
637
|
const { workstream, workstreamRef, orgRef, userRef, userName } = params
|
|
305
638
|
const runtimeAdapters = getRuntimeAdapters()
|
|
@@ -320,7 +653,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
320
653
|
})
|
|
321
654
|
|
|
322
655
|
let inputMessage: ChatMessage | undefined
|
|
323
|
-
const shouldPersistInputMessage = params.kind === 'userTurn' ? params.
|
|
656
|
+
const shouldPersistInputMessage = params.kind === 'userTurn' ? params.skipInputMessagePersistence !== true : false
|
|
324
657
|
const shouldProcessPostRunSideEffects =
|
|
325
658
|
params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage
|
|
326
659
|
if (params.kind === 'userTurn') {
|
|
@@ -373,7 +706,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
373
706
|
id: inputMessage.id || Bun.randomUUIDv7(),
|
|
374
707
|
role: 'user',
|
|
375
708
|
parts: inputMessage.parts,
|
|
376
|
-
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) },
|
|
709
|
+
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) ?? Date.now() },
|
|
377
710
|
}
|
|
378
711
|
}
|
|
379
712
|
|
|
@@ -395,14 +728,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
395
728
|
: validateUIMessages<ChatMessage>({
|
|
396
729
|
messages: persistedLiveHistory,
|
|
397
730
|
metadataSchema: messageMetadataSchema,
|
|
398
|
-
dataSchemas:
|
|
731
|
+
dataSchemas: dataPartsSchemas,
|
|
399
732
|
}).then((messages) => messages.map(hydrateMessageFileUrls)),
|
|
400
733
|
persistedRecentHistory.length === 0
|
|
401
734
|
? Promise.resolve([] as ChatMessage[])
|
|
402
735
|
: validateUIMessages<ChatMessage>({
|
|
403
736
|
messages: persistedRecentHistory,
|
|
404
737
|
metadataSchema: messageMetadataSchema,
|
|
405
|
-
dataSchemas:
|
|
738
|
+
dataSchemas: dataPartsSchemas,
|
|
406
739
|
}).then((messages) => messages.map(hydrateMessageFileUrls)),
|
|
407
740
|
])
|
|
408
741
|
timer.step('validate+hydrate-history')
|
|
@@ -413,7 +746,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
413
746
|
}
|
|
414
747
|
|
|
415
748
|
const originalMessages = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
416
|
-
|
|
749
|
+
let allAssistantMessages: ChatMessage[] = []
|
|
417
750
|
const referenceUserMessage =
|
|
418
751
|
params.kind === 'userTurn' && !shouldPersistInputMessage
|
|
419
752
|
? [...liveHistory].reverse().find((m) => m.role === 'user')
|
|
@@ -437,14 +770,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
437
770
|
if (workstream.core && !workstream.coreType) {
|
|
438
771
|
throw new WorkstreamTurnError('Core workstreams require a core type.', 400)
|
|
439
772
|
}
|
|
440
|
-
const coreWorkstreamProfile:
|
|
441
|
-
workstream.core && workstream.coreType
|
|
442
|
-
? (getCoreWorkstreamProfile(workstream.coreType) as unknown as {
|
|
443
|
-
config: { agentId: string }
|
|
444
|
-
instructions: string
|
|
445
|
-
skills?: string[]
|
|
446
|
-
})
|
|
447
|
-
: null
|
|
773
|
+
const coreWorkstreamProfile: CoreWorkstreamProfile | null =
|
|
774
|
+
workstream.core && workstream.coreType ? getCoreWorkstreamProfile(workstream.coreType) : null
|
|
448
775
|
const defaultLeadAgentId = getLeadAgentId()
|
|
449
776
|
const visibleWorkstreamAgentId =
|
|
450
777
|
workstream.mode === 'direct' ? workstream.agentId : (coreWorkstreamProfile?.config.agentId ?? defaultLeadAgentId)
|
|
@@ -626,7 +953,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
626
953
|
|
|
627
954
|
const section = await learnedSkillService
|
|
628
955
|
.retrieveForTurn({ orgId: orgIdString, agentId, query: messageText, limit: 3, minConfidence: 0.6 })
|
|
629
|
-
.catch((error
|
|
956
|
+
.catch((error) => {
|
|
630
957
|
aiLogger.warn`Failed to retrieve learned skills for ${agentId}: ${error}`
|
|
631
958
|
return undefined
|
|
632
959
|
})
|
|
@@ -697,175 +1024,33 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
697
1024
|
})
|
|
698
1025
|
|
|
699
1026
|
await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [committed] })
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
currentMessages[currentMessageIndex] = committed
|
|
703
|
-
} else {
|
|
704
|
-
currentMessages = [...currentMessages, committed]
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const assistantIndex = allAssistantMessages.findIndex((item) => item.id === committed.id)
|
|
708
|
-
if (assistantIndex >= 0) {
|
|
709
|
-
allAssistantMessages[assistantIndex] = committed
|
|
710
|
-
} else {
|
|
711
|
-
allAssistantMessages.push(committed)
|
|
712
|
-
}
|
|
1027
|
+
currentMessages = upsertChatHistoryMessage(currentMessages, committed)
|
|
1028
|
+
allAssistantMessages = upsertChatHistoryMessage(allAssistantMessages, committed)
|
|
713
1029
|
|
|
714
1030
|
return committed
|
|
715
1031
|
}
|
|
716
1032
|
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
workstreamRef,
|
|
739
|
-
orgRef,
|
|
740
|
-
userRef,
|
|
741
|
-
userName,
|
|
742
|
-
onboardingActive,
|
|
743
|
-
linearInstalled,
|
|
744
|
-
githubInstalled,
|
|
745
|
-
reasoningProfile: reasoningProfile.name,
|
|
746
|
-
skills: streamParams.skills,
|
|
747
|
-
additionalInstructionSections: streamParams.additionalInstructionSections,
|
|
748
|
-
context: buildContextResult,
|
|
749
|
-
}),
|
|
750
|
-
)
|
|
751
|
-
agentTimer.step('hook:resolveAgent')
|
|
752
|
-
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
|
|
753
|
-
const [preSeededMemoriesSection, workstreamStateSection, learnedSkillsSection] = await Promise.all([
|
|
754
|
-
getPreSeededMemoriesSection(resolvedAgentId),
|
|
755
|
-
getWorkstreamStateSection(),
|
|
756
|
-
getLearnedSkillsSection(resolvedAgentId),
|
|
757
|
-
])
|
|
758
|
-
agentTimer.step('parallel-fetch(memories+state+skills)')
|
|
759
|
-
const config = getAgentRuntimeConfig({
|
|
760
|
-
agentId: resolvedAgentId,
|
|
761
|
-
workstreamMode: workstream.mode,
|
|
762
|
-
mode: streamParams.mode,
|
|
763
|
-
skills: streamParams.skills,
|
|
764
|
-
onboardingActive,
|
|
765
|
-
linearInstalled,
|
|
766
|
-
reasoningProfile: reasoningProfile.name,
|
|
767
|
-
systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
|
|
768
|
-
preSeededMemoriesSection,
|
|
769
|
-
retrievedKnowledgeSection,
|
|
770
|
-
workstreamMemoryBlock: memoryBlock,
|
|
771
|
-
workstreamStateSection,
|
|
772
|
-
learnedSkillsSection,
|
|
773
|
-
additionalInstructionSections: mergeInstructionSections(
|
|
774
|
-
executionPlanInstructionSections,
|
|
775
|
-
streamParams.additionalInstructionSections,
|
|
776
|
-
hookInstructionSections,
|
|
777
|
-
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
778
|
-
optionalInstructionSection(agentResolution?.extraInstructions),
|
|
779
|
-
),
|
|
780
|
-
context: buildContextResult,
|
|
781
|
-
}) as AgentRuntimeConfig
|
|
782
|
-
agentTimer.step('build-agent-config')
|
|
783
|
-
const modelMessages = await convertToModelMessages(streamParams.messages, {
|
|
784
|
-
ignoreIncompleteToolCalls: true,
|
|
785
|
-
})
|
|
786
|
-
agentTimer.step('convert-model-messages')
|
|
787
|
-
const agent = (createAgent as unknown as AgentFactory)[config.id as string]({
|
|
788
|
-
mode: streamParams.mode,
|
|
789
|
-
tools: streamParams.tools,
|
|
790
|
-
extraInstructions: config.extraInstructions,
|
|
791
|
-
stopWhen: (agentResolution?.stopWhen as
|
|
792
|
-
| StopCondition<ToolSet>
|
|
793
|
-
| Array<StopCondition<ToolSet>>
|
|
794
|
-
| undefined) ??
|
|
795
|
-
streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
|
|
796
|
-
prepareStep:
|
|
797
|
-
(agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
|
|
798
|
-
})
|
|
799
|
-
const agentAbortSignal = streamParams.abortSignal ?? runAbort.signal
|
|
800
|
-
agentTimer.step('agent-construction')
|
|
801
|
-
|
|
802
|
-
let result: unknown
|
|
803
|
-
try {
|
|
804
|
-
result = await streamParams.observer.run(() =>
|
|
805
|
-
agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
|
|
806
|
-
)
|
|
807
|
-
agentTimer.step('agent.stream()-resolved')
|
|
808
|
-
} catch (error) {
|
|
809
|
-
if (agentAbortSignal.aborted) {
|
|
810
|
-
streamParams.observer.recordAbort(error)
|
|
811
|
-
} else {
|
|
812
|
-
streamParams.observer.recordError(error)
|
|
813
|
-
}
|
|
814
|
-
throw error
|
|
815
|
-
}
|
|
816
|
-
if (!hasUIMessageStream(result)) {
|
|
817
|
-
throw new Error(`Agent run for ${resolvedAgentId} did not expose a UI message stream.`)
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
let responseMessage: ChatMessage | null = null
|
|
821
|
-
let resolveFinishedStream!: () => void
|
|
822
|
-
const finishedStream = new Promise<void>((resolve) => {
|
|
823
|
-
resolveFinishedStream = resolve
|
|
824
|
-
})
|
|
825
|
-
|
|
826
|
-
const uiStream = result.toUIMessageStream({
|
|
827
|
-
generateMessageId: () => Bun.randomUUIDv7(),
|
|
828
|
-
originalMessages: streamParams.messages,
|
|
829
|
-
sendReasoning: true,
|
|
830
|
-
sendSources: true,
|
|
831
|
-
messageMetadata: createAgentMessageMetadata({
|
|
832
|
-
agentId: resolvedAgentId,
|
|
833
|
-
agentName: config.displayName as string,
|
|
834
|
-
}),
|
|
835
|
-
onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
|
|
836
|
-
responseMessage = withMessageCreatedAt(finishedResponseMessage)
|
|
837
|
-
resolveFinishedStream()
|
|
838
|
-
},
|
|
839
|
-
}) as ReadableStream<ChatStreamChunk>
|
|
840
|
-
const reader = uiStream.getReader()
|
|
841
|
-
let firstChunkLogged = false
|
|
842
|
-
try {
|
|
843
|
-
for (;;) {
|
|
844
|
-
const { done, value } = await reader.read()
|
|
845
|
-
if (done) break
|
|
846
|
-
if (!firstChunkLogged) {
|
|
847
|
-
agentTimer.step('first-stream-chunk')
|
|
848
|
-
firstChunkLogged = true
|
|
849
|
-
}
|
|
850
|
-
if (streamParams.writer) {
|
|
851
|
-
streamParams.writer.write(value)
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
} finally {
|
|
855
|
-
reader.releaseLock()
|
|
856
|
-
}
|
|
857
|
-
agentTimer.step('stream-complete')
|
|
858
|
-
|
|
859
|
-
const finalizedResponseMessage = await finishedStream.then(() => responseMessage)
|
|
860
|
-
if (finalizedResponseMessage === null) {
|
|
861
|
-
throw new Error(`Agent run for ${resolvedAgentId} did not produce a response message.`)
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
for (const toolError of collectToolOutputErrors({ responseMessage: finalizedResponseMessage })) {
|
|
865
|
-
aiLogger.warn`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
return finalizedResponseMessage
|
|
1033
|
+
const streamCtx: StreamAgentResponseContext = {
|
|
1034
|
+
turnHooks,
|
|
1035
|
+
workstream,
|
|
1036
|
+
workstreamRef,
|
|
1037
|
+
orgRef,
|
|
1038
|
+
userRef,
|
|
1039
|
+
userName,
|
|
1040
|
+
onboardingActive,
|
|
1041
|
+
linearInstalled,
|
|
1042
|
+
githubInstalled,
|
|
1043
|
+
reasoningProfileName: reasoningProfile.name,
|
|
1044
|
+
buildContextResult,
|
|
1045
|
+
getExecutionPlanInstructionSections,
|
|
1046
|
+
getPreSeededMemoriesSection,
|
|
1047
|
+
getWorkstreamStateSection,
|
|
1048
|
+
getLearnedSkillsSection,
|
|
1049
|
+
promptContext,
|
|
1050
|
+
retrievedKnowledgeSection,
|
|
1051
|
+
memoryBlock,
|
|
1052
|
+
hookInstructionSections,
|
|
1053
|
+
runAbortSignal: runAbort.signal,
|
|
869
1054
|
}
|
|
870
1055
|
|
|
871
1056
|
const runVisibleAgent = async (runParams: {
|
|
@@ -907,7 +1092,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
907
1092
|
...runParams.extraTools,
|
|
908
1093
|
}
|
|
909
1094
|
visibleTimer.step('build-agent-tools')
|
|
910
|
-
|
|
1095
|
+
streamCtx.memoryBlock = memoryBlock
|
|
1096
|
+
const responseMessage = await streamAgentResponse(streamCtx, {
|
|
911
1097
|
agentId: runParams.agentId,
|
|
912
1098
|
mode: runParams.mode,
|
|
913
1099
|
messages: buildRunInputMessages(runParams.extraMessages),
|
|
@@ -1126,14 +1312,15 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1126
1312
|
contextCompactionRuntime.shouldCompactHistory({
|
|
1127
1313
|
summaryText,
|
|
1128
1314
|
liveMessages: messages,
|
|
1129
|
-
contextSize:
|
|
1315
|
+
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
1130
1316
|
}),
|
|
1131
|
-
enqueueCompaction: () =>
|
|
1132
|
-
enqueueContextCompaction({
|
|
1317
|
+
enqueueCompaction: async () => {
|
|
1318
|
+
await enqueueContextCompaction({
|
|
1133
1319
|
domain: 'workstream',
|
|
1134
1320
|
entityId: workstreamIdString,
|
|
1135
|
-
contextSize:
|
|
1136
|
-
})
|
|
1321
|
+
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
1322
|
+
})
|
|
1323
|
+
},
|
|
1137
1324
|
unregisterRun: (runId) => chatRunRegistry.unregister(runId),
|
|
1138
1325
|
clearActiveRunId: (runId) => workstreamService.clearActiveRunIdIfMatches(workstreamRef, runId),
|
|
1139
1326
|
disposeAbort: () => runAbort.dispose(),
|
|
@@ -1143,130 +1330,30 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1143
1330
|
})
|
|
1144
1331
|
|
|
1145
1332
|
if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
enqueuePostChatMemory({
|
|
1171
|
-
orgId: orgIdString,
|
|
1172
|
-
workstreamId: workstreamIdString,
|
|
1173
|
-
sourceId: referenceUserMessageId,
|
|
1174
|
-
onboardStatus: readOptionalString((workspace as { onboardStatus?: unknown }).onboardStatus),
|
|
1175
|
-
userMessage: memoryUserMessage,
|
|
1176
|
-
historyMessages: historyMessagesForMemory,
|
|
1177
|
-
agentMessages,
|
|
1178
|
-
memoryBlock: memoryBlock.trim() ? memoryBlock : undefined,
|
|
1179
|
-
attachmentContext: attachmentMetadataContext,
|
|
1180
|
-
}),
|
|
1181
|
-
{ operationName: 'post-chat memory extraction enqueue' },
|
|
1182
|
-
)
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
if (params.kind === 'userTurn' && referenceUserMessage) {
|
|
1186
|
-
const conversationSummary = buildConversationSummary({
|
|
1187
|
-
userMessageText,
|
|
1188
|
-
assistantMessages: allAssistantMessages,
|
|
1189
|
-
})
|
|
1190
|
-
if (conversationSummary) {
|
|
1191
|
-
const recentActivityResult = await recentActivityService.recordEvent({
|
|
1192
|
-
orgId: orgRef,
|
|
1193
|
-
userId: userRef,
|
|
1194
|
-
source: 'system',
|
|
1195
|
-
event: {
|
|
1196
|
-
sourceEventId: `chat-turn:${referenceUserMessageId}`,
|
|
1197
|
-
kind: 'chat.turn.completed',
|
|
1198
|
-
targetKind: 'workstream',
|
|
1199
|
-
targetId: workstreamIdString,
|
|
1200
|
-
mergeKey: `workstream:${workstreamIdString}`,
|
|
1201
|
-
title: buildRecentActivityChatSystemTitle({
|
|
1202
|
-
workstream,
|
|
1203
|
-
visibleAgentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1204
|
-
}),
|
|
1205
|
-
sourceLabel: agentDisplayNames[visibleWorkstreamAgentId ?? defaultLeadAgentId],
|
|
1206
|
-
deepLink: buildRecentActivityChatDeepLink({
|
|
1207
|
-
workstream,
|
|
1208
|
-
workstreamId: workstreamIdString,
|
|
1209
|
-
visibleAgentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1210
|
-
}),
|
|
1211
|
-
metadata: {
|
|
1212
|
-
agentId: visibleWorkstreamAgentId ?? defaultLeadAgentId,
|
|
1213
|
-
agentName: agentDisplayNames[visibleWorkstreamAgentId ?? defaultLeadAgentId],
|
|
1214
|
-
workstreamId: workstreamIdString,
|
|
1215
|
-
workstreamTitle: latestWorkstreamRecord.title ?? workstream.title,
|
|
1216
|
-
workstreamMode: workstream.mode,
|
|
1217
|
-
...(workstream.coreType ? { coreType: workstream.coreType } : {}),
|
|
1218
|
-
userMessageText,
|
|
1219
|
-
assistantSummary: conversationSummary,
|
|
1220
|
-
messageId: referenceUserMessageId,
|
|
1221
|
-
},
|
|
1222
|
-
occurredAt: toIsoDateTimeString(referenceUserMessage.metadata?.createdAt ?? Date.now()),
|
|
1223
|
-
},
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
await safeEnqueue(
|
|
1227
|
-
async () => {
|
|
1228
|
-
const enqueuePostChatOrgAction = getRuntimeAdapters().queues?.enqueuePostChatOrgAction
|
|
1229
|
-
if (!enqueuePostChatOrgAction) {
|
|
1230
|
-
return
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
await enqueuePostChatOrgAction({
|
|
1234
|
-
orgId: orgIdString,
|
|
1235
|
-
workstreamId: workstreamIdString,
|
|
1236
|
-
sourceId: referenceUserMessageId,
|
|
1237
|
-
sourceCreatedAt: referenceUserMessage.metadata?.createdAt ?? Date.now(),
|
|
1238
|
-
conversationSummary,
|
|
1239
|
-
})
|
|
1240
|
-
},
|
|
1241
|
-
{ operationName: 'post-chat org action enqueue' },
|
|
1242
|
-
)
|
|
1243
|
-
|
|
1244
|
-
if (recentActivityService.isMeaningfulRefinementCandidate(recentActivityResult.item)) {
|
|
1245
|
-
await safeEnqueue(
|
|
1246
|
-
() => enqueueRecentActivityTitleRefinement({ activityId: recentActivityResult.item.id }),
|
|
1247
|
-
{ operationName: 'recent activity title refinement enqueue' },
|
|
1248
|
-
)
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
if (shouldEnqueueRegularDigestForWorkstream({ onboardingActive, turnCount })) {
|
|
1254
|
-
await safeEnqueue(() => enqueueRegularChatMemoryDigest({ orgId: orgIdString }), {
|
|
1255
|
-
operationName: 'regular chat memory digest enqueue',
|
|
1256
|
-
})
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
if (shouldEnqueueSkillExtraction({ onboardingActive, turnCount })) {
|
|
1260
|
-
await safeEnqueue(() => enqueueSkillExtraction({ orgId: orgIdString }), {
|
|
1261
|
-
operationName: 'skill extraction enqueue',
|
|
1262
|
-
})
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
if (shouldEnqueueMemoryConsolidation({ onboardingActive, turnCount })) {
|
|
1266
|
-
await safeEnqueue(() => enqueueMemoryConsolidation({ scopeId: orgIdString }), {
|
|
1267
|
-
operationName: 'memory consolidation enqueue',
|
|
1268
|
-
})
|
|
1269
|
-
}
|
|
1333
|
+
await runPostTurnSideEffects({
|
|
1334
|
+
workstream,
|
|
1335
|
+
workstreamRef,
|
|
1336
|
+
orgRef,
|
|
1337
|
+
userRef,
|
|
1338
|
+
userName,
|
|
1339
|
+
orgIdString,
|
|
1340
|
+
workstreamIdString,
|
|
1341
|
+
onboardingActive,
|
|
1342
|
+
workspace,
|
|
1343
|
+
allAssistantMessages,
|
|
1344
|
+
referenceUserMessage,
|
|
1345
|
+
referenceUserMessageId,
|
|
1346
|
+
recentHistory,
|
|
1347
|
+
listReadableUploads: () => listReadableUploads(),
|
|
1348
|
+
memoryBlock,
|
|
1349
|
+
visibleWorkstreamAgentId,
|
|
1350
|
+
defaultLeadAgentId,
|
|
1351
|
+
latestWorkstreamRecord,
|
|
1352
|
+
latestPersistedState,
|
|
1353
|
+
turnHooks,
|
|
1354
|
+
buildContextResult,
|
|
1355
|
+
isUserTurn: params.kind === 'userTurn',
|
|
1356
|
+
})
|
|
1270
1357
|
}
|
|
1271
1358
|
|
|
1272
1359
|
if (allAssistantMessages.length > 0) {
|