@lota-sdk/core 0.1.8 → 0.1.11

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.
Files changed (38) hide show
  1. package/infrastructure/schema/00_workstream.surql +2 -1
  2. package/infrastructure/schema/02_execution_plan.surql +202 -52
  3. package/package.json +4 -2
  4. package/src/bifrost/bifrost.ts +94 -25
  5. package/src/config/model-constants.ts +8 -6
  6. package/src/db/memory-store.ts +3 -71
  7. package/src/db/service.ts +42 -2
  8. package/src/db/tables.ts +9 -2
  9. package/src/embeddings/provider.ts +92 -21
  10. package/src/index.ts +6 -0
  11. package/src/redis/stream-context.ts +44 -0
  12. package/src/runtime/approval-continuation.ts +59 -0
  13. package/src/runtime/chat-request-routing.ts +5 -1
  14. package/src/runtime/execution-plan.ts +21 -14
  15. package/src/runtime/turn-lifecycle.ts +14 -6
  16. package/src/runtime/workstream-chat-helpers.ts +5 -5
  17. package/src/services/context-compaction.service.ts +6 -2
  18. package/src/services/document-chunk.service.ts +2 -2
  19. package/src/services/execution-plan.service.ts +579 -786
  20. package/src/services/learned-skill.service.ts +2 -2
  21. package/src/services/plan-approval.service.ts +83 -0
  22. package/src/services/plan-artifact.service.ts +45 -0
  23. package/src/services/plan-builder.service.ts +61 -0
  24. package/src/services/plan-checkpoint.service.ts +53 -0
  25. package/src/services/plan-compiler.service.ts +81 -0
  26. package/src/services/plan-executor.service.ts +1623 -0
  27. package/src/services/plan-run.service.ts +422 -0
  28. package/src/services/plan-validator.service.ts +760 -0
  29. package/src/services/workstream-turn-preparation.ts +70 -196
  30. package/src/services/workstream-turn.ts +12 -0
  31. package/src/services/workstream.service.ts +24 -182
  32. package/src/services/workstream.types.ts +2 -65
  33. package/src/system-agents/title-generator.agent.ts +2 -2
  34. package/src/tools/execution-plan.tool.ts +20 -46
  35. package/src/tools/log-hello-world.tool.ts +17 -0
  36. package/src/workers/skill-extraction.runner.ts +2 -2
  37. package/src/services/workstream-change-tracker.service.ts +0 -313
  38. package/src/system-agents/workstream-tracker.agent.ts +0 -58
@@ -40,7 +40,7 @@ import { hasApprovalRespondedParts } from '../runtime/approval-continuation'
40
40
  import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataText } from '../runtime/chat-attachments'
41
41
  import { hasMessageContent } from '../runtime/chat-message'
42
42
  import { waitForCompactionIfNeeded } from '../runtime/chat-run-orchestration'
43
- import { mergeStateDelta, parseWorkstreamState } from '../runtime/context-compaction'
43
+ import { parseWorkstreamState } from '../runtime/context-compaction'
44
44
  import { CONTEXT_SIZE } from '../runtime/context-compaction-constants'
45
45
  import { createExecutionPlanInstructionSectionCache } from '../runtime/execution-plan'
46
46
  import { mergeInstructionSections } from '../runtime/instruction-sections'
@@ -54,13 +54,12 @@ import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/r
54
54
  import { shouldEnqueueSkillExtraction } from '../runtime/skill-extraction-policy'
55
55
  import { finalizeTurnRun } from '../runtime/turn-lifecycle'
56
56
  import {
57
- appendCompactionContextToHistoryMessages,
57
+ appendPersistedWorkstreamContextToHistoryMessages,
58
58
  buildAgentHistoryMessages,
59
59
  buildConversationSummary,
60
60
  buildReadableUploadMetadataContext,
61
61
  collectToolOutputErrors,
62
62
  extractMessageText,
63
- extractTrackerMessageText,
64
63
  toHistoryMessages,
65
64
  toOptionalTrimmedString,
66
65
  } from '../runtime/workstream-chat-helpers'
@@ -69,8 +68,7 @@ import {
69
68
  classifyPolicyClasses,
70
69
  resolveReasoningProfile,
71
70
  } from '../runtime/workstream-routing-policy'
72
- import { WorkstreamStateSchema } from '../runtime/workstream-state'
73
- import type { WorkstreamState, WorkstreamStateDelta } from '../runtime/workstream-state'
71
+ import type { WorkstreamState } from '../runtime/workstream-state'
74
72
  import { chatRunRegistry } from '../services/chat-run-registry.service'
75
73
  import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstream.types'
76
74
  import { createTeamThinkTool } from '../tools/team-think.tool'
@@ -84,7 +82,6 @@ import { executionPlanService } from './execution-plan.service'
84
82
  import { learnedSkillService } from './learned-skill.service'
85
83
  import { memoryService } from './memory.service'
86
84
  import { recentActivityService } from './recent-activity.service'
87
- import { updateWorkstreamChangeTracker } from './workstream-change-tracker.service'
88
85
  import { workstreamMessageService } from './workstream-message.service'
89
86
  import { workstreamService } from './workstream.service'
90
87
 
@@ -143,58 +140,13 @@ export function parsePersistedWorkstreamState(value: unknown): WorkstreamState |
143
140
  return parseWorkstreamState(value)
144
141
  }
145
142
 
146
- export function extractFirstAbsoluteUrl(text: string): URL | null {
147
- const match = text.match(/https?:\/\/[^\s)]+/i)
148
- if (!match) return null
143
+ export function stripExecutionPlanFieldsFromWorkstreamState(
144
+ state: WorkstreamState | null | undefined,
145
+ hasExecutionPlan: boolean,
146
+ ): WorkstreamState | null | undefined {
147
+ if (!state || !hasExecutionPlan) return state
149
148
 
150
- const normalized = match[0].replace(/[.,;!?]+$/g, '')
151
- try {
152
- return new URL(normalized)
153
- } catch {
154
- return null
155
- }
156
- }
157
-
158
- export function taskRequestsWebsiteRefresh(task: string, forceRefresh: boolean): boolean {
159
- return forceRefresh || /\b(refresh|re-run|rerun|run again|extract again|re-extract|overwrite|recrawl)\b/i.test(task)
160
- }
161
-
162
- export function buildInspectWebsiteTrackerTitle(input: unknown): string {
163
- if (!input || typeof input !== 'object') return 'Inspect website intelligence'
164
-
165
- const task = typeof (input as { task?: unknown }).task === 'string' ? (input as { task: string }).task.trim() : ''
166
- const forceRefresh = (input as { forceRefresh?: unknown }).forceRefresh === true
167
- const hostname = extractFirstAbsoluteUrl(task)?.hostname.replace(/^www\./, '') ?? null
168
-
169
- if (hostname && taskRequestsWebsiteRefresh(task, forceRefresh)) {
170
- return `Overwrite website-intelligence artifacts for ${hostname}`
171
- }
172
- if (hostname) {
173
- return `Inspect website intelligence for ${hostname}`
174
- }
175
- return taskRequestsWebsiteRefresh(task, forceRefresh)
176
- ? 'Overwrite website-intelligence artifacts'
177
- : 'Inspect website intelligence'
178
- }
179
-
180
- export function emitTransientWorkstreamTrackerState(params: {
181
- writer?: UIMessageStreamWriter<ChatMessage>
182
- workstreamRecord: WorkstreamRecord
183
- existingState: WorkstreamState
184
- delta: WorkstreamStateDelta
185
- }): WorkstreamState {
186
- const nextState = mergeStateDelta(params.existingState, params.delta, () => Date.now())
187
-
188
- if (params.writer) {
189
- params.writer.write({
190
- type: 'data-workstreamTracker',
191
- data: workstreamService.toPublicWorkstreamDetail({ ...params.workstreamRecord, state: nextState })
192
- .workstreamState as unknown as Record<string, unknown>,
193
- transient: true,
194
- })
195
- }
196
-
197
- return nextState
149
+ return { ...state, currentPlan: null, tasks: [], artifacts: [] }
198
150
  }
199
151
 
200
152
  export async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
@@ -277,6 +229,7 @@ export interface WorkstreamTurnParams {
277
229
  inputMessage: ChatMessage
278
230
  persistInputMessage?: boolean
279
231
  abortSignal?: AbortSignal
232
+ streamId?: string
280
233
  }
281
234
 
282
235
  export interface WorkstreamApprovalContinuationParams {
@@ -287,6 +240,7 @@ export interface WorkstreamApprovalContinuationParams {
287
240
  userName?: string | null
288
241
  approvalMessages: ChatMessage[]
289
242
  abortSignal?: AbortSignal
243
+ streamId?: string
290
244
  }
291
245
 
292
246
  export type WorkstreamRunCoreParams = {
@@ -296,9 +250,11 @@ export type WorkstreamRunCoreParams = {
296
250
  userRef: RecordIdRef
297
251
  userName?: string | null
298
252
  abortSignal?: AbortSignal
253
+ streamId?: string
299
254
  } & (
300
255
  | { kind: 'userTurn'; inputMessage: ChatMessage; persistInputMessage?: boolean }
301
256
  | { kind: 'approvalContinuation'; approvalMessages: ChatMessage[] }
257
+ | { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
302
258
  )
303
259
 
304
260
  export interface PreparedWorkstreamTurn {
@@ -364,7 +320,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
364
320
 
365
321
  let inputMessage: ChatMessage | undefined
366
322
  const shouldPersistInputMessage = params.kind === 'userTurn' ? params.persistInputMessage !== false : false
367
- const shouldProcessPostRunSideEffects = params.kind === 'approvalContinuation' || shouldPersistInputMessage
323
+ const shouldProcessPostRunSideEffects =
324
+ params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage
368
325
  if (params.kind === 'userTurn') {
369
326
  inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage))
370
327
  if (inputMessage.role !== 'user') {
@@ -383,7 +340,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
383
340
  throw new WorkstreamTurnError('A chat run is already active.', 409)
384
341
  }
385
342
 
386
- if (params.kind === 'approvalContinuation') {
343
+ if (params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn') {
387
344
  const approvedAssistantMessage = [...params.approvalMessages]
388
345
  .reverse()
389
346
  .find((m) => m.role === 'assistant' && hasApprovalRespondedParts(m))
@@ -395,9 +352,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
395
352
 
396
353
  const workspacePromise = workspaceProvider ? workspaceProvider.getWorkspace(orgRef) : Promise.resolve({})
397
354
  const initialWorkstreamState = parsePersistedWorkstreamState(workstreamRecord.state)
355
+ const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
398
356
  const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
399
357
  workstreamRef,
400
- toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined,
358
+ persistedCompactionCursor,
401
359
  )
402
360
  const persistedRecentHistoryPromise = workstreamMessageService.listRecentMessages(workstreamRef, 64)
403
361
 
@@ -595,11 +553,38 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
595
553
  disabled: onboardingActive,
596
554
  loadPlan: async () => await executionPlanService.getActivePlanForWorkstream(workstreamRef),
597
555
  })
556
+ const getExecutionPlan = async () => await executionPlanInstructionSectionCache.getPlan()
598
557
  const getExecutionPlanInstructionSections = async (): Promise<string[] | undefined> =>
599
558
  await executionPlanInstructionSectionCache.getSections()
600
559
  const invalidateExecutionPlanInstructionSections = () => {
601
560
  executionPlanInstructionSectionCache.invalidate()
602
561
  }
562
+ const getWorkstreamStateSection = async (): Promise<string | undefined> => {
563
+ const executionPlan = await getExecutionPlan()
564
+ return contextCompactionRuntime.formatWorkstreamStateForPrompt(
565
+ stripExecutionPlanFieldsFromWorkstreamState(workstreamState, Boolean(executionPlan)),
566
+ )
567
+ }
568
+ const respondedBy = recordIdToString(userRef, TABLES.USER)
569
+ if (params.kind === 'approvalContinuation') {
570
+ const appliedApproval = await executionPlanService.applyApprovalResponseFromMessages({
571
+ workstreamId: workstreamRef,
572
+ approvalMessages: params.approvalMessages,
573
+ respondedBy,
574
+ })
575
+ if (appliedApproval) {
576
+ invalidateExecutionPlanInstructionSections()
577
+ }
578
+ } else if (userMessage) {
579
+ const appliedHumanInput = await executionPlanService.applyHumanInputFromUserMessage({
580
+ workstreamId: workstreamRef,
581
+ message: userMessage,
582
+ respondedBy,
583
+ })
584
+ if (appliedHumanInput) {
585
+ invalidateExecutionPlanInstructionSections()
586
+ }
587
+ }
603
588
  const preSeededMemoriesByAgent = new Map<string, string | undefined>()
604
589
  const getPreSeededMemoriesSection = async (agentId: string): Promise<string | undefined> => {
605
590
  if (preSeededMemoriesByAgent.has(agentId)) {
@@ -630,9 +615,12 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
630
615
  return section
631
616
  }
632
617
 
633
- const persistedChatSummary = typeof workstreamRecord.chatSummary === 'string' ? workstreamRecord.chatSummary : ''
618
+ const persistedCompactionSummary =
619
+ persistedCompactionCursor && typeof workstreamRecord.compactionSummary === 'string'
620
+ ? workstreamRecord.compactionSummary
621
+ : ''
634
622
  const messagesForContext = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
635
- let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedChatSummary, messagesForContext)
623
+ let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedCompactionSummary, messagesForContext)
636
624
  const referenceUserMessageId = referenceUserMessage?.id ?? ''
637
625
  const listReadableUploads = (extraMessages: ChatMessage[] = []) =>
638
626
  listReadableUploadsFromChatMessages({
@@ -654,6 +642,9 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
654
642
  const serverRunId = Bun.randomUUIDv7()
655
643
  const runAbort = createServerRunAbortController(params.abortSignal)
656
644
  await workstreamService.setActiveRunId(workstreamRef, serverRunId)
645
+ if (params.streamId) {
646
+ await workstreamService.setActiveStreamId(workstreamRef, params.streamId)
647
+ }
657
648
  chatRunRegistry.register(serverRunId, runAbort.controller)
658
649
 
659
650
  try {
@@ -745,7 +736,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
745
736
  preSeededMemoriesSection: await getPreSeededMemoriesSection(resolvedAgentId),
746
737
  retrievedKnowledgeSection,
747
738
  workstreamMemoryBlock: memoryBlock,
748
- workstreamStateSection: contextCompactionRuntime.formatWorkstreamStateForPrompt(workstreamState),
739
+ workstreamStateSection: await getWorkstreamStateSection(),
749
740
  learnedSkillsSection: await getLearnedSkillsSection(resolvedAgentId),
750
741
  additionalInstructionSections: mergeInstructionSections(
751
742
  executionPlanInstructionSections,
@@ -811,8 +802,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
811
802
  },
812
803
  }) as ReadableStream<ChatStreamChunk>
813
804
  const reader = uiStream.getReader()
814
- let liveTrackerState: WorkstreamState = workstreamState ?? parseWorkstreamState(null)
815
- const liveTrackedToolCalls = new Map<string, string>()
816
805
  try {
817
806
  for (;;) {
818
807
  const { done, value } = await reader.read()
@@ -820,71 +809,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
820
809
  if (streamParams.writer) {
821
810
  streamParams.writer.write(value)
822
811
  }
823
- if (value.type === 'tool-input-available' && value.toolName === 'inspectWebsite') {
824
- const title = buildInspectWebsiteTrackerTitle(value.input)
825
- liveTrackedToolCalls.set(value.toolCallId, title)
826
- liveTrackerState = emitTransientWorkstreamTrackerState({
827
- writer: streamParams.writer,
828
- workstreamRecord,
829
- existingState: liveTrackerState,
830
- delta: {
831
- taskUpdates: [
832
- {
833
- title,
834
- status: 'in-progress',
835
- owner: streamParams.agentId,
836
- externalId: value.toolCallId,
837
- sourceMessageIds: [],
838
- },
839
- ],
840
- },
841
- })
842
- }
843
- if (
844
- value.type === 'tool-output-available' &&
845
- value.preliminary !== true &&
846
- liveTrackedToolCalls.has(value.toolCallId)
847
- ) {
848
- liveTrackerState = emitTransientWorkstreamTrackerState({
849
- writer: streamParams.writer,
850
- workstreamRecord,
851
- existingState: liveTrackerState,
852
- delta: {
853
- taskUpdates: [
854
- {
855
- title: liveTrackedToolCalls.get(value.toolCallId) ?? 'Inspect website intelligence',
856
- status: 'done',
857
- owner: streamParams.agentId,
858
- externalId: value.toolCallId,
859
- sourceMessageIds: [],
860
- },
861
- ],
862
- },
863
- })
864
- liveTrackedToolCalls.delete(value.toolCallId)
865
- }
866
- if (
867
- (value.type === 'tool-output-error' || value.type === 'tool-output-denied') &&
868
- liveTrackedToolCalls.has(value.toolCallId)
869
- ) {
870
- liveTrackerState = emitTransientWorkstreamTrackerState({
871
- writer: streamParams.writer,
872
- workstreamRecord,
873
- existingState: liveTrackerState,
874
- delta: {
875
- taskUpdates: [
876
- {
877
- title: liveTrackedToolCalls.get(value.toolCallId) ?? 'Inspect website intelligence',
878
- status: 'blocked',
879
- owner: streamParams.agentId,
880
- externalId: value.toolCallId,
881
- sourceMessageIds: [],
882
- },
883
- ],
884
- },
885
- })
886
- liveTrackedToolCalls.delete(value.toolCallId)
887
- }
888
812
  }
889
813
  } finally {
890
814
  reader.releaseLock()
@@ -959,6 +883,12 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
959
883
  )
960
884
  }
961
885
 
886
+ // Execution-plan approval continuations mutate plan state and persist the approval message,
887
+ // but they do not begin a new visible agent turn.
888
+ if (params.kind === 'approvalContinuation') {
889
+ return
890
+ }
891
+
962
892
  const consultSpecialistTool = createTool({
963
893
  description: 'Consult one specialist teammate for domain-specific guidance before replying to the user.',
964
894
  inputSchema: ConsultSpecialistArgsSchema,
@@ -1003,7 +933,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1003
933
  preSeededMemoriesSection: await getPreSeededMemoriesSection(agentId),
1004
934
  retrievedKnowledgeSection,
1005
935
  workstreamMemoryBlock: specialistMemoryBlock,
1006
- workstreamStateSection: contextCompactionRuntime.formatWorkstreamStateForPrompt(workstreamState),
936
+ workstreamStateSection: await getWorkstreamStateSection(),
1007
937
  learnedSkillsSection: await getLearnedSkillsSection(agentId),
1008
938
  additionalInstructionSections: mergeInstructionSections(
1009
939
  specialistExecutionPlanInstructionSections,
@@ -1115,17 +1045,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1115
1045
  throw new WorkstreamTurnError('Direct workstreams require an assigned agent.', 400)
1116
1046
  }
1117
1047
  await runVisibleAgent({ agentId: workstream.agentId, mode: 'direct' })
1118
- } else if (params.kind === 'userTurn') {
1119
- await runVisibleAgent({
1120
- agentId: visibleWorkstreamAgentId ?? 'chief',
1121
- mode: 'workstreamMode',
1122
- skills: coreWorkstreamProfile?.skills ? [...coreWorkstreamProfile.skills] : undefined,
1123
- additionalInstructionSections: mergeInstructionSections(coreInstructionSections, hookInstructionSections),
1124
- extraTools: {
1125
- [CONSULT_SPECIALIST_TOOL_NAME]: consultSpecialistTool,
1126
- ...(teamThinkTool ? { [CONSULT_TEAM_TOOL_NAME]: teamThinkTool } : {}),
1127
- },
1128
- })
1129
1048
  } else {
1130
1049
  await runVisibleAgent({
1131
1050
  agentId: visibleWorkstreamAgentId ?? 'chief',
@@ -1141,9 +1060,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1141
1060
  } finally {
1142
1061
  try {
1143
1062
  const latestWorkstreamRecord = await workstreamService.getById(workstreamRef)
1144
- const latestPersistedState = WorkstreamStateSchema.safeParse(latestWorkstreamRecord.state).success
1145
- ? WorkstreamStateSchema.parse(latestWorkstreamRecord.state)
1146
- : null
1063
+ const latestPersistedState = parsePersistedWorkstreamState(latestWorkstreamRecord.state)
1147
1064
 
1148
1065
  await finalizeTurnRun({
1149
1066
  serverRunId,
@@ -1165,60 +1082,17 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1165
1082
  unregisterRun: (runId) => chatRunRegistry.unregister(runId),
1166
1083
  clearActiveRunId: (runId) => workstreamService.clearActiveRunIdIfMatches(workstreamRef, runId),
1167
1084
  disposeAbort: () => runAbort.dispose(),
1085
+ activeStreamId: params.streamId,
1086
+ clearActiveStreamId: (streamId) =>
1087
+ workstreamService.clearActiveStreamIdIfMatches(workstreamRef, streamId),
1168
1088
  })
1169
1089
 
1170
- let trackerAwareWorkstreamRecord = latestWorkstreamRecord
1171
- let trackerAwarePersistedState = latestPersistedState
1172
-
1173
- if (!onboardingActive && allAssistantMessages.length > 0) {
1174
- const activeExecutionPlan = await executionPlanService.getActivePlanForWorkstream(workstreamRef)
1175
- const trackerUpdated = await updateWorkstreamChangeTracker({
1176
- workstreamId: workstreamRef,
1177
- title: latestWorkstreamRecord.title ?? workstream.title,
1178
- mode: workstream.mode,
1179
- ...(workstream.coreType ? { coreType: workstream.coreType } : {}),
1180
- ...(visibleWorkstreamAgentId ? { visibleAgentId: visibleWorkstreamAgentId } : {}),
1181
- hasActiveExecutionPlan: activeExecutionPlan !== null,
1182
- previousSummary:
1183
- typeof latestWorkstreamRecord.chatSummary === 'string' ? latestWorkstreamRecord.chatSummary : null,
1184
- existingState: latestPersistedState,
1185
- userMessageText: referenceUserMessage ? extractMessageText(referenceUserMessage).trim() : null,
1186
- assistantMessages: allAssistantMessages
1187
- .map((message) => {
1188
- const text = extractTrackerMessageText(message).trim()
1189
- if (!text) return null
1190
- const label =
1191
- typeof message.metadata?.agentName === 'string' && message.metadata.agentName.trim().length > 0
1192
- ? message.metadata.agentName.trim()
1193
- : typeof message.metadata?.agentId === 'string' && message.metadata.agentId.trim().length > 0
1194
- ? message.metadata.agentId.trim()
1195
- : (visibleWorkstreamAgentId ?? 'Assistant')
1196
- return { label, text }
1197
- })
1198
- .filter((message): message is { label: string; text: string } => Boolean(message)),
1199
- })
1200
-
1201
- if (trackerUpdated) {
1202
- const trackedWorkstreamRecord = await workstreamService.getWorkstreamRecord(workstreamRef)
1203
- trackerAwareWorkstreamRecord = trackedWorkstreamRecord
1204
- trackerAwarePersistedState = parsePersistedWorkstreamState(trackedWorkstreamRecord.state)
1205
- if (writer) {
1206
- writer.write({
1207
- type: 'data-workstreamTracker',
1208
- data: workstreamService.toPublicWorkstreamDetail(trackedWorkstreamRecord)
1209
- .workstreamState as unknown as Record<string, unknown>,
1210
- transient: true,
1211
- })
1212
- }
1213
- }
1214
- }
1215
-
1216
1090
  if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
1217
1091
  const turnCount = await workstreamService.incrementTurnCount(workstreamRef)
1218
1092
  const agentMessages = buildAgentHistoryMessages(allAssistantMessages)
1219
- const historyMessagesForMemory = appendCompactionContextToHistoryMessages(
1093
+ const historyMessagesForMemory = appendPersistedWorkstreamContextToHistoryMessages(
1220
1094
  toHistoryMessages(recentHistory),
1221
- { chatSummary: trackerAwareWorkstreamRecord.chatSummary, persistedState: trackerAwarePersistedState },
1095
+ { compactionSummary: latestWorkstreamRecord.compactionSummary, persistedState: latestPersistedState },
1222
1096
  )
1223
1097
 
1224
1098
  const userMessageText = referenceUserMessage ? extractMessageText(referenceUserMessage).trim() : ''
@@ -1283,7 +1157,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1283
1157
  agentId: visibleWorkstreamAgentId ?? 'chief',
1284
1158
  agentName: agentDisplayNames[visibleWorkstreamAgentId ?? 'chief'],
1285
1159
  workstreamId: workstreamIdString,
1286
- workstreamTitle: trackerAwareWorkstreamRecord.title ?? workstream.title,
1160
+ workstreamTitle: latestWorkstreamRecord.title ?? workstream.title,
1287
1161
  workstreamMode: workstream.mode,
1288
1162
  ...(workstream.coreType ? { coreType: workstream.coreType } : {}),
1289
1163
  userMessageText,
@@ -1350,8 +1224,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1350
1224
  onboardingActive,
1351
1225
  referenceUserMessage,
1352
1226
  assistantMessages: allAssistantMessages,
1353
- latestWorkstreamRecord: trackerAwareWorkstreamRecord,
1354
- latestPersistedState: trackerAwarePersistedState,
1227
+ latestWorkstreamRecord,
1228
+ latestPersistedState,
1355
1229
  context: buildContextResult,
1356
1230
  })
1357
1231
  }
@@ -21,6 +21,18 @@ export async function createWorkstreamApprovalContinuationStream(params: Workstr
21
21
  })
22
22
  }
23
23
 
24
+ export async function createWorkstreamNativeToolApprovalStream(params: WorkstreamApprovalContinuationParams) {
25
+ const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'nativeToolApprovalTurn' })
26
+
27
+ return createUIMessageStream<ChatMessage>({
28
+ originalMessages: prepared.originalMessages,
29
+ onError: (error) => (error instanceof Error ? error.message : 'Native tool approval stream failed.'),
30
+ execute: async ({ writer }) => {
31
+ await prepared.run(writer)
32
+ },
33
+ })
34
+ }
35
+
24
36
  export async function createWorkstreamTurnStream(params: WorkstreamTurnParams) {
25
37
  const prepared = await prepareWorkstreamRunCore({ ...params, kind: 'userTurn' })
26
38