@lota-sdk/core 0.1.7 → 0.1.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.
@@ -13,7 +13,7 @@ DEFINE FIELD IF NOT EXISTS updatedAt ON TABLE workstream TYPE datetime VALUE tim
13
13
  DEFINE FIELD IF NOT EXISTS memoryBlock ON TABLE workstream TYPE option<string>;
14
14
  DEFINE FIELD IF NOT EXISTS memoryBlockSummary ON TABLE workstream TYPE option<string>;
15
15
  DEFINE FIELD IF NOT EXISTS activeRunId ON TABLE workstream TYPE option<string>;
16
- DEFINE FIELD IF NOT EXISTS chatSummary ON TABLE workstream TYPE option<string>;
16
+ DEFINE FIELD IF NOT EXISTS compactionSummary ON TABLE workstream TYPE option<string>;
17
17
  DEFINE FIELD IF NOT EXISTS lastCompactedMessageId ON TABLE workstream TYPE option<string>;
18
18
  DEFINE FIELD IF NOT EXISTS nameGenerated ON TABLE workstream TYPE bool DEFAULT false;
19
19
  DEFINE FIELD IF NOT EXISTS isCompacting ON TABLE workstream TYPE bool DEFAULT false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -114,7 +114,7 @@
114
114
  "dependencies": {
115
115
  "@ai-sdk/openai": "^3.0.41",
116
116
  "@logtape/logtape": "^2.0.4",
117
- "@lota-sdk/shared": "file:../shared",
117
+ "@lota-sdk/shared": "0.1.7",
118
118
  "@mendable/firecrawl-js": "^4.16.0",
119
119
  "@surrealdb/node": "^3.0.3",
120
120
  "ai": "^6.0.116",
@@ -2,7 +2,7 @@ import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
2
2
 
3
3
  export async function finalizeTurnRun(params: {
4
4
  serverRunId: string
5
- getEntity: () => Promise<{ lastCompactedMessageId?: string | null; chatSummary?: string | null }>
5
+ getEntity: () => Promise<{ lastCompactedMessageId?: string | null; compactionSummary?: string | null }>
6
6
  getUncompactedMessages: (cursor?: string) => Promise<ChatMessage[]>
7
7
  assessCompaction: (summaryText: string, messages: ChatMessage[]) => { shouldCompact: boolean }
8
8
  enqueueCompaction: () => Promise<void>
@@ -14,7 +14,7 @@ export async function finalizeTurnRun(params: {
14
14
  const entity = await params.getEntity()
15
15
  const cursor = typeof entity.lastCompactedMessageId === 'string' ? entity.lastCompactedMessageId : undefined
16
16
  const uncompactedMessages = await params.getUncompactedMessages(cursor)
17
- const summaryText = typeof entity.chatSummary === 'string' ? entity.chatSummary : ''
17
+ const summaryText = typeof entity.compactionSummary === 'string' ? entity.compactionSummary : ''
18
18
  const { shouldCompact } = params.assessCompaction(summaryText, uncompactedMessages)
19
19
 
20
20
  if (shouldCompact) {
@@ -121,14 +121,14 @@ export function buildAgentHistoryMessages(messages: ChatMessageLike[]): Array<{
121
121
  .map((message) => ({ content: message.content, ...(message.agentName ? { agentName: message.agentName } : {}) }))
122
122
  }
123
123
 
124
- export function appendCompactionContextToHistoryMessages(
124
+ export function appendPersistedWorkstreamContextToHistoryMessages(
125
125
  historyMessages: WorkstreamHistoryMessage[],
126
- params: { chatSummary?: string | null; persistedState?: unknown },
126
+ params: { compactionSummary?: string | null; persistedState?: unknown },
127
127
  ): WorkstreamHistoryMessage[] {
128
128
  const nextHistoryMessages = [...historyMessages]
129
- const chatSummary = typeof params.chatSummary === 'string' ? params.chatSummary.trim() : ''
130
- if (chatSummary) {
131
- nextHistoryMessages.push({ role: 'agent', content: `Compacted chat summary:\n${chatSummary}` })
129
+ const compactionSummary = typeof params.compactionSummary === 'string' ? params.compactionSummary.trim() : ''
130
+ if (compactionSummary) {
131
+ nextHistoryMessages.push({ role: 'agent', content: `Compacted chat summary:\n${compactionSummary}` })
132
132
  }
133
133
 
134
134
  if (params.persistedState !== undefined && params.persistedState !== null) {
@@ -61,7 +61,7 @@ class ContextCompactionService {
61
61
  )
62
62
 
63
63
  const result = await contextCompactionRuntime.compactHistory({
64
- summaryText: typeof workstream.chatSummary === 'string' ? workstream.chatSummary : '',
64
+ summaryText: typeof workstream.compactionSummary === 'string' ? workstream.compactionSummary : '',
65
65
  liveMessages,
66
66
  tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
67
67
  contextSize: params.contextSize,
@@ -82,7 +82,11 @@ class ContextCompactionService {
82
82
  await databaseService.update(
83
83
  TABLES.WORKSTREAM,
84
84
  params.workstreamId,
85
- { chatSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId, state: result.state },
85
+ {
86
+ compactionSummary: result.summaryText,
87
+ lastCompactedMessageId: result.lastCompactedMessageId,
88
+ state: result.state,
89
+ },
86
90
  WorkstreamSchema,
87
91
  )
88
92
 
@@ -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,60 +140,6 @@ 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
149
-
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
198
- }
199
-
200
143
  export async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
201
144
  return await waitForCompactionIfNeeded({
202
145
  entityId: recordIdToString(workstreamId, TABLES.WORKSTREAM),
@@ -395,9 +338,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
395
338
 
396
339
  const workspacePromise = workspaceProvider ? workspaceProvider.getWorkspace(orgRef) : Promise.resolve({})
397
340
  const initialWorkstreamState = parsePersistedWorkstreamState(workstreamRecord.state)
341
+ const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
398
342
  const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
399
343
  workstreamRef,
400
- toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined,
344
+ persistedCompactionCursor,
401
345
  )
402
346
  const persistedRecentHistoryPromise = workstreamMessageService.listRecentMessages(workstreamRef, 64)
403
347
 
@@ -630,9 +574,12 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
630
574
  return section
631
575
  }
632
576
 
633
- const persistedChatSummary = typeof workstreamRecord.chatSummary === 'string' ? workstreamRecord.chatSummary : ''
577
+ const persistedCompactionSummary =
578
+ persistedCompactionCursor && typeof workstreamRecord.compactionSummary === 'string'
579
+ ? workstreamRecord.compactionSummary
580
+ : ''
634
581
  const messagesForContext = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
635
- let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedChatSummary, messagesForContext)
582
+ let currentMessages = contextCompactionRuntime.prependSummaryMessage(persistedCompactionSummary, messagesForContext)
636
583
  const referenceUserMessageId = referenceUserMessage?.id ?? ''
637
584
  const listReadableUploads = (extraMessages: ChatMessage[] = []) =>
638
585
  listReadableUploadsFromChatMessages({
@@ -811,8 +758,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
811
758
  },
812
759
  }) as ReadableStream<ChatStreamChunk>
813
760
  const reader = uiStream.getReader()
814
- let liveTrackerState: WorkstreamState = workstreamState ?? parseWorkstreamState(null)
815
- const liveTrackedToolCalls = new Map<string, string>()
816
761
  try {
817
762
  for (;;) {
818
763
  const { done, value } = await reader.read()
@@ -820,71 +765,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
820
765
  if (streamParams.writer) {
821
766
  streamParams.writer.write(value)
822
767
  }
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
768
  }
889
769
  } finally {
890
770
  reader.releaseLock()
@@ -1141,9 +1021,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1141
1021
  } finally {
1142
1022
  try {
1143
1023
  const latestWorkstreamRecord = await workstreamService.getById(workstreamRef)
1144
- const latestPersistedState = WorkstreamStateSchema.safeParse(latestWorkstreamRecord.state).success
1145
- ? WorkstreamStateSchema.parse(latestWorkstreamRecord.state)
1146
- : null
1024
+ const latestPersistedState = parsePersistedWorkstreamState(latestWorkstreamRecord.state)
1147
1025
 
1148
1026
  await finalizeTurnRun({
1149
1027
  serverRunId,
@@ -1167,58 +1045,12 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1167
1045
  disposeAbort: () => runAbort.dispose(),
1168
1046
  })
1169
1047
 
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
1048
  if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
1217
1049
  const turnCount = await workstreamService.incrementTurnCount(workstreamRef)
1218
1050
  const agentMessages = buildAgentHistoryMessages(allAssistantMessages)
1219
- const historyMessagesForMemory = appendCompactionContextToHistoryMessages(
1051
+ const historyMessagesForMemory = appendPersistedWorkstreamContextToHistoryMessages(
1220
1052
  toHistoryMessages(recentHistory),
1221
- { chatSummary: trackerAwareWorkstreamRecord.chatSummary, persistedState: trackerAwarePersistedState },
1053
+ { compactionSummary: latestWorkstreamRecord.compactionSummary, persistedState: latestPersistedState },
1222
1054
  )
1223
1055
 
1224
1056
  const userMessageText = referenceUserMessage ? extractMessageText(referenceUserMessage).trim() : ''
@@ -1283,7 +1115,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1283
1115
  agentId: visibleWorkstreamAgentId ?? 'chief',
1284
1116
  agentName: agentDisplayNames[visibleWorkstreamAgentId ?? 'chief'],
1285
1117
  workstreamId: workstreamIdString,
1286
- workstreamTitle: trackerAwareWorkstreamRecord.title ?? workstream.title,
1118
+ workstreamTitle: latestWorkstreamRecord.title ?? workstream.title,
1287
1119
  workstreamMode: workstream.mode,
1288
1120
  ...(workstream.coreType ? { coreType: workstream.coreType } : {}),
1289
1121
  userMessageText,
@@ -1350,8 +1182,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1350
1182
  onboardingActive,
1351
1183
  referenceUserMessage,
1352
1184
  assistantMessages: allAssistantMessages,
1353
- latestWorkstreamRecord: trackerAwareWorkstreamRecord,
1354
- latestPersistedState: trackerAwarePersistedState,
1185
+ latestWorkstreamRecord,
1186
+ latestPersistedState,
1355
1187
  context: buildContextResult,
1356
1188
  })
1357
1189
  }
@@ -20,23 +20,12 @@ import {
20
20
  parseMemoryBlock,
21
21
  serializeMemoryBlock,
22
22
  } from '../runtime/memory-block'
23
- import { toOptionalTrimmedString } from '../runtime/workstream-chat-helpers'
24
- import { WorkstreamStateSchema } from '../runtime/workstream-state'
25
- import type { WorkstreamState } from '../runtime/workstream-state'
26
23
  import { toIsoDateTimeString } from '../utils/date-time'
27
24
  import { chatRunRegistry } from './chat-run-registry.service'
28
25
  import { contextCompactionService } from './context-compaction.service'
29
26
  import { workstreamMessageService } from './workstream-message.service'
30
27
  import { WorkstreamSchema, WorkstreamStatusSchema } from './workstream.types'
31
- import type {
32
- NormalizedWorkstream,
33
- PublicWorkstreamApprovalState,
34
- PublicWorkstreamDetail,
35
- PublicWorkstreamStateFocus,
36
- PublicWorkstreamStatePayload,
37
- PublicWorkstreamStateProgress,
38
- WorkstreamRecord,
39
- } from './workstream.types'
28
+ import type { NormalizedWorkstream, WorkstreamRecord } from './workstream.types'
40
29
 
41
30
  // Uses SurrealQL directly to keep pagination/order logic close to queries.
42
31
 
@@ -146,155 +135,6 @@ function buildCoreWorkstreamId({
146
135
  return new RecordId(TABLES.WORKSTREAM, `core_${typeValue}_user_${userValue}_organization_${orgValue}`)
147
136
  }
148
137
 
149
- function toOptionalIsoDateTimeString(value: number | null | undefined): string | null {
150
- if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return null
151
- return toIsoDateTimeString(new Date(value))
152
- }
153
-
154
- function getCompactedSummaryFocus(chatSummary: string | null): string | null {
155
- if (!chatSummary) return null
156
-
157
- const lines = chatSummary
158
- .split('\n')
159
- .map((line) => line.trim())
160
- .filter(Boolean)
161
-
162
- for (const line of lines) {
163
- if (line.endsWith(':')) continue
164
- if (line.startsWith('- ')) {
165
- return toOptionalTrimmedString(line.slice(2))
166
- }
167
- return toOptionalTrimmedString(line)
168
- }
169
-
170
- return null
171
- }
172
-
173
- function parsePersistedWorkstreamState(value: unknown): WorkstreamState | null {
174
- const parsed = WorkstreamStateSchema.safeParse(value)
175
- return parsed.success ? parsed.data : null
176
- }
177
-
178
- function buildEmptyWorkstreamStateProgress(): PublicWorkstreamStateProgress {
179
- return {
180
- hasState: false,
181
- lastUpdated: null,
182
- completionRatio: null,
183
- tasks: { total: 0, open: 0, inProgress: 0, done: 0, blocked: 0 },
184
- constraints: { total: 0, approved: 0, candidate: 0 },
185
- keyDecisions: 0,
186
- openQuestions: 0,
187
- risks: 0,
188
- artifacts: 0,
189
- agentContributions: 0,
190
- }
191
- }
192
-
193
- function buildWorkstreamStateProgress(state: WorkstreamState | null): PublicWorkstreamStateProgress {
194
- if (!state) {
195
- return buildEmptyWorkstreamStateProgress()
196
- }
197
-
198
- const tasks = {
199
- total: state.tasks.length,
200
- open: state.tasks.filter((task) => task.status === 'open').length,
201
- inProgress: state.tasks.filter((task) => task.status === 'in-progress').length,
202
- done: state.tasks.filter((task) => task.status === 'done').length,
203
- blocked: state.tasks.filter((task) => task.status === 'blocked').length,
204
- }
205
- const constraintsApproved = state.activeConstraints.filter((constraint) => constraint.approved).length
206
-
207
- return {
208
- hasState:
209
- state.currentPlan !== null ||
210
- state.activeConstraints.length > 0 ||
211
- state.keyDecisions.length > 0 ||
212
- state.tasks.length > 0 ||
213
- state.openQuestions.length > 0 ||
214
- state.risks.length > 0 ||
215
- state.artifacts.length > 0 ||
216
- state.agentContributions.length > 0 ||
217
- toOptionalTrimmedString(state.approvedBy) !== null ||
218
- typeof state.approvedAt === 'number' ||
219
- toOptionalTrimmedString(state.approvalMessageId) !== null ||
220
- toOptionalTrimmedString(state.approvalNote) !== null,
221
- lastUpdated: toOptionalIsoDateTimeString(state.lastUpdated),
222
- completionRatio: tasks.total > 0 ? Number((tasks.done / tasks.total).toFixed(4)) : null,
223
- tasks,
224
- constraints: {
225
- total: state.activeConstraints.length,
226
- approved: constraintsApproved,
227
- candidate: state.activeConstraints.length - constraintsApproved,
228
- },
229
- keyDecisions: state.keyDecisions.length,
230
- openQuestions: state.openQuestions.length,
231
- risks: state.risks.length,
232
- artifacts: state.artifacts.length,
233
- agentContributions: state.agentContributions.length,
234
- }
235
- }
236
-
237
- function buildWorkstreamApprovalState(state: WorkstreamState | null): PublicWorkstreamApprovalState | null {
238
- if (!state) return null
239
-
240
- const approvedBy = toOptionalTrimmedString(state.approvedBy)
241
- const approvedAt = toOptionalIsoDateTimeString(state.approvedAt)
242
- const approvalMessageId = toOptionalTrimmedString(state.approvalMessageId)
243
- const approvalNote = toOptionalTrimmedString(state.approvalNote)
244
-
245
- if (!approvedBy && !approvedAt && !approvalMessageId && !approvalNote) {
246
- return null
247
- }
248
-
249
- return { approvedBy, approvedAt, approvalMessageId, approvalNote }
250
- }
251
-
252
- function buildWorkstreamStateFocus(
253
- state: WorkstreamState | null,
254
- chatSummary: string | null,
255
- ): PublicWorkstreamStateFocus | null {
256
- if (!state) {
257
- const compactedSummaryFocus = getCompactedSummaryFocus(chatSummary)
258
- return compactedSummaryFocus ? { kind: 'chat-summary', text: compactedSummaryFocus } : null
259
- }
260
-
261
- const currentPlan = toOptionalTrimmedString(state.currentPlan?.text)
262
- if (currentPlan) {
263
- return { kind: 'plan', text: currentPlan }
264
- }
265
-
266
- const latestTask =
267
- [...state.tasks].reverse().find((task) => task.status === 'blocked') ??
268
- [...state.tasks].reverse().find((task) => task.status === 'in-progress') ??
269
- [...state.tasks].reverse().find((task) => task.status === 'open')
270
- const taskTitle = toOptionalTrimmedString(latestTask?.title)
271
- if (taskTitle) {
272
- return { kind: 'task', text: taskTitle }
273
- }
274
-
275
- const latestQuestion = toOptionalTrimmedString(state.openQuestions.at(-1)?.text)
276
- if (latestQuestion) {
277
- return { kind: 'question', text: latestQuestion }
278
- }
279
-
280
- const latestDecision = toOptionalTrimmedString(state.keyDecisions.at(-1)?.decision)
281
- if (latestDecision) {
282
- return { kind: 'decision', text: latestDecision }
283
- }
284
-
285
- const latestAgentNote = toOptionalTrimmedString(state.agentContributions.at(-1)?.summary)
286
- if (latestAgentNote) {
287
- return { kind: 'agent-note', text: latestAgentNote }
288
- }
289
-
290
- const compactedSummaryFocus = getCompactedSummaryFocus(chatSummary)
291
- if (compactedSummaryFocus) {
292
- return { kind: 'chat-summary', text: compactedSummaryFocus }
293
- }
294
-
295
- return null
296
- }
297
-
298
138
  class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
299
139
  constructor() {
300
140
  super(TABLES.WORKSTREAM, WorkstreamSchema)
@@ -664,14 +504,6 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
664
504
  await this.setActiveRunId(workstreamId, null)
665
505
  }
666
506
 
667
- async persistChangeTracker(
668
- workstreamId: RecordIdRef,
669
- payload: { chatSummary: string; state: WorkstreamState },
670
- ): Promise<void> {
671
- const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
672
- await this.update(workstreamRef, { chatSummary: payload.chatSummary, state: payload.state })
673
- }
674
-
675
507
  async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
676
508
  const activeRunId = await this.getActiveRunId(workstreamId)
677
509
  if (!activeRunId) return false
@@ -868,22 +700,6 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
868
700
  }
869
701
  }
870
702
 
871
- toPublicWorkstreamDetail(workstream: WorkstreamRecord): PublicWorkstreamDetail {
872
- const publicWorkstream = this.toPublicWorkstream(workstream)
873
- const snapshot = parsePersistedWorkstreamState(workstream.state)
874
- const chatSummary = toOptionalTrimmedString(workstream.chatSummary)
875
- const progress = buildWorkstreamStateProgress(snapshot)
876
- const workstreamState: PublicWorkstreamStatePayload = {
877
- focus: buildWorkstreamStateFocus(snapshot, chatSummary),
878
- chatSummary,
879
- approval: buildWorkstreamApprovalState(snapshot),
880
- progress,
881
- snapshot,
882
- }
883
-
884
- return { ...publicWorkstream, workstreamState }
885
- }
886
-
887
703
  async incrementTurnCount(workstreamId: RecordIdRef): Promise<number> {
888
704
  const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
889
705
  const result = await databaseService.query<{ turnCount: number }>(surql`
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod'
2
2
 
3
- import type { WorkstreamState } from '../runtime/workstream-state'
4
-
5
3
  export interface Citation {
6
4
  title?: string
7
5
  url?: string
@@ -33,68 +31,6 @@ export interface NormalizedWorkstream {
33
31
  organizationId: string
34
32
  }
35
33
 
36
- export interface PublicWorkstreamStateFocus {
37
- kind: 'plan' | 'task' | 'question' | 'decision' | 'agent-note' | 'chat-summary'
38
- text: string
39
- }
40
-
41
- export interface PublicWorkstreamTaskProgress {
42
- total: number
43
- open: number
44
- inProgress: number
45
- done: number
46
- blocked: number
47
- }
48
-
49
- export interface PublicWorkstreamConstraintProgress {
50
- total: number
51
- approved: number
52
- candidate: number
53
- }
54
-
55
- export interface PublicWorkstreamStateProgress {
56
- hasState: boolean
57
- lastUpdated: string | null
58
- completionRatio: number | null
59
- tasks: PublicWorkstreamTaskProgress
60
- constraints: PublicWorkstreamConstraintProgress
61
- keyDecisions: number
62
- openQuestions: number
63
- risks: number
64
- artifacts: number
65
- agentContributions: number
66
- }
67
-
68
- export interface PublicWorkstreamApprovalState {
69
- approvedBy: string | null
70
- approvedAt: string | null
71
- approvalMessageId: string | null
72
- approvalNote: string | null
73
- }
74
-
75
- export interface PublicWorkstreamStatePayload {
76
- focus: PublicWorkstreamStateFocus | null
77
- chatSummary: string | null
78
- approval: PublicWorkstreamApprovalState | null
79
- progress: PublicWorkstreamStateProgress
80
- snapshot: WorkstreamState | null
81
- }
82
-
83
- export interface PublicWorkstreamDetail {
84
- id: string
85
- title: string
86
- status: 'regular' | 'archived'
87
- mode: 'direct' | 'group'
88
- core: boolean
89
- coreType?: string
90
- isRunning: boolean
91
- isCompacting: boolean
92
- agentId?: string | null
93
- createdAt: string
94
- updatedAt: string
95
- workstreamState: PublicWorkstreamStatePayload
96
- }
97
-
98
34
  export const WorkstreamSchema = z.object({
99
35
  id: z.any(), // RecordId
100
36
  mode: WorkstreamModeSchema.optional().default('group'),
@@ -106,7 +42,7 @@ export const WorkstreamSchema = z.object({
106
42
  memoryBlock: z.string().nullish(),
107
43
  memoryBlockSummary: z.string().nullish(),
108
44
  activeRunId: z.string().nullish(),
109
- chatSummary: z.string().nullish(),
45
+ compactionSummary: z.string().nullish(),
110
46
  lastCompactedMessageId: z.string().nullish(),
111
47
  nameGenerated: z.boolean().optional().default(false),
112
48
  isCompacting: z.boolean().optional(),
@@ -1,313 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- import { aiLogger } from '../config/logger'
4
- import type { RecordIdRef } from '../db/record-id'
5
- import { mergeStateDelta } from '../runtime/context-compaction'
6
- import { createHelperModelRuntime, extractJsonObjectCandidates } from '../runtime/helper-model'
7
- import { toOptionalTrimmedString } from '../runtime/workstream-chat-helpers'
8
- import {
9
- StructuredWorkstreamStateDeltaSchema,
10
- createEmptyStructuredWorkstreamStateDelta,
11
- createEmptyWorkstreamState,
12
- parseStructuredWorkstreamStateDelta,
13
- } from '../runtime/workstream-state'
14
- import type { WorkstreamState, WorkstreamStateDelta } from '../runtime/workstream-state'
15
- import { createWorkstreamTrackerAgent } from '../system-agents/workstream-tracker.agent'
16
- import { compactWhitespace, isRecord, truncateText } from '../utils/string'
17
- import { workstreamService } from './workstream.service'
18
-
19
- const helperModelRuntime = createHelperModelRuntime()
20
-
21
- const TrackerOutputSchema = z.object({
22
- summary: z.string().trim().min(1).max(1_200),
23
- stateDelta: StructuredWorkstreamStateDeltaSchema,
24
- })
25
-
26
- type TrackerMessage = { label: string; text: string }
27
-
28
- const TRACKER_JSON_WRAPPER_KEYS = ['output', 'result', 'data'] as const
29
- const TRACKER_SUMMARY_KEYS = ['summary', 'chatSummary', 'summaryText', 'sidebarSummary', 'message'] as const
30
- const TRACKER_STATE_DELTA_KEYS = ['stateDelta', 'delta', 'workstreamStateDelta', 'trackerStateDelta', 'state'] as const
31
- const TRACKER_FALLBACK_SECTION_PREFIX =
32
- /^(?:#{1,6}\s*)?(?:\*\*)?(?:state delta|current plan|new decisions|resolved questions|new questions|new constraints|new risks|task updates|artifacts|agent note|conflicts|approved by|approved at|approval message id|approval note)(?:\*\*)?\s*:?\s*/i
33
-
34
- function renderMessages(messages: TrackerMessage[]): string {
35
- if (messages.length === 0) return '- None'
36
- return messages
37
- .map((message, index) => `### Message ${index + 1}\n- Actor: ${message.label}\n- Content: ${message.text}`)
38
- .join('\n\n')
39
- }
40
-
41
- function getRecordStringField(record: Record<string, unknown>, keys: readonly string[]): string | null {
42
- for (const key of keys) {
43
- const field = toOptionalTrimmedString(typeof record[key] === 'string' ? record[key] : null)
44
- if (field) return field
45
- }
46
-
47
- return null
48
- }
49
-
50
- function getTrackerJsonObjects(value: unknown): Record<string, unknown>[] {
51
- if (!isRecord(value)) return []
52
-
53
- const candidates: Record<string, unknown>[] = [value]
54
- for (const key of TRACKER_JSON_WRAPPER_KEYS) {
55
- const nested = value[key]
56
- if (isRecord(nested)) {
57
- candidates.push(nested)
58
- }
59
- }
60
-
61
- return candidates
62
- }
63
-
64
- function parseTrackerJsonFallback(text: string): z.infer<typeof TrackerOutputSchema> | null {
65
- for (const candidateText of extractJsonObjectCandidates(text)) {
66
- let parsedJson: unknown
67
-
68
- try {
69
- parsedJson = JSON.parse(candidateText) as unknown
70
- } catch {
71
- continue
72
- }
73
-
74
- for (const candidate of getTrackerJsonObjects(parsedJson)) {
75
- const summary = getRecordStringField(candidate, TRACKER_SUMMARY_KEYS)
76
- if (!summary) continue
77
-
78
- const stateDeltaValue = TRACKER_STATE_DELTA_KEYS.map((key) => candidate[key]).find((value) => value !== undefined)
79
- const parsedStateDelta = StructuredWorkstreamStateDeltaSchema.safeParse(stateDeltaValue)
80
-
81
- return {
82
- summary: truncateText(summary, 1_200),
83
- stateDelta: parsedStateDelta.success ? parsedStateDelta.data : createEmptyStructuredWorkstreamStateDelta(),
84
- }
85
- }
86
- }
87
-
88
- return null
89
- }
90
-
91
- function extractTrackerFallbackSummary(text: string): string | null {
92
- const normalized = text.replace(/\r/g, '').trim()
93
- if (!normalized) return null
94
-
95
- const summaryLineMatch = normalized.match(/(?:^|\n)(?:#{1,6}\s*)?(?:\*\*)?summary(?:\*\*)?\s*:?\s*(.+?)(?=\n|$)/i)
96
- const summaryLine = summaryLineMatch?.[1]?.trim()
97
- if (summaryLine && !summaryLine.startsWith('{') && !summaryLine.startsWith('```')) {
98
- return truncateText(summaryLine, 1_200)
99
- }
100
-
101
- const summarySectionMatch = normalized.match(
102
- /(?:^|\n)(?:#{1,6}\s*)?(?:\*\*)?summary(?:\*\*)?\s*:?\s*\n([\s\S]*?)(?=\n(?:#{1,6}\s*)?(?:\*\*)?(?:state delta|summary)(?:\*\*)?\s*:?\s*\n|$)/i,
103
- )
104
- const summarySource = summarySectionMatch?.[1]?.trim() || normalized
105
- const paragraphs = summarySource
106
- .split(/\n\s*\n/)
107
- .map((paragraph) => paragraph.trim())
108
- .filter((paragraph) => paragraph.length > 0)
109
-
110
- for (const paragraph of paragraphs) {
111
- const candidate = paragraph.replace(/^(?:#{1,6}\s*)?(?:\*\*)?summary(?:\*\*)?\s*:?\s*/i, '').trim()
112
- if (
113
- !candidate ||
114
- candidate.startsWith('{') ||
115
- candidate.startsWith('```') ||
116
- TRACKER_FALLBACK_SECTION_PREFIX.test(candidate)
117
- ) {
118
- continue
119
- }
120
-
121
- return truncateText(candidate, 1_200)
122
- }
123
-
124
- return null
125
- }
126
-
127
- export function parseTrackerTextFallback(text: string): z.infer<typeof TrackerOutputSchema> | null {
128
- const jsonFallback = parseTrackerJsonFallback(text)
129
- if (jsonFallback) {
130
- return jsonFallback
131
- }
132
-
133
- const summary = extractTrackerFallbackSummary(text)
134
- if (!summary) return null
135
-
136
- return { summary, stateDelta: createEmptyStructuredWorkstreamStateDelta() }
137
- }
138
-
139
- function extractHeuristicSummaryCandidate(text: string | null | undefined): string | null {
140
- const normalized = compactWhitespace(toOptionalTrimmedString(text) ?? '')
141
- if (!normalized) return null
142
-
143
- const match = /^(.+?[.!?])(?:\s|$)/.exec(normalized)
144
- const candidate = match ? String(match[1]) : normalized
145
- return truncateText(candidate, 1_200)
146
- }
147
-
148
- export function buildHeuristicTrackerSummary(params: {
149
- previousSummary: string | null
150
- userMessageText: string | null
151
- assistantMessages: TrackerMessage[]
152
- }): string | null {
153
- for (const message of params.assistantMessages) {
154
- const summary = extractHeuristicSummaryCandidate(message.text)
155
- if (summary) {
156
- return summary
157
- }
158
- }
159
-
160
- const userSummary = extractHeuristicSummaryCandidate(params.userMessageText)
161
- if (userSummary) {
162
- return truncateText(`User request: ${userSummary}`, 1_200)
163
- }
164
-
165
- return extractHeuristicSummaryCandidate(params.previousSummary)
166
- }
167
-
168
- function formatTrackerPrompt(params: {
169
- title: string
170
- mode: 'direct' | 'group'
171
- coreType?: string
172
- visibleAgentId?: string
173
- hasActiveExecutionPlan: boolean
174
- previousSummary: string | null
175
- existingState: WorkstreamState
176
- userMessageText: string | null
177
- assistantMessages: TrackerMessage[]
178
- }): string {
179
- return [
180
- '# Workstream Turn',
181
- '',
182
- `- Title: ${params.title}`,
183
- `- Mode: ${params.mode}`,
184
- `- Visible agent: ${params.visibleAgentId ?? 'none'}`,
185
- `- Active execution plan: ${params.hasActiveExecutionPlan ? 'yes' : 'no'}`,
186
- ...(params.coreType ? [`- Core type: ${params.coreType}`] : []),
187
- '',
188
- '## Previous Summary',
189
- params.previousSummary ?? 'None',
190
- '',
191
- '## Existing State',
192
- JSON.stringify(params.existingState),
193
- '',
194
- '## User Message',
195
- params.userMessageText ?? 'None',
196
- '',
197
- '## Assistant Messages',
198
- renderMessages(params.assistantMessages),
199
- ...(params.hasActiveExecutionPlan
200
- ? [
201
- '',
202
- '## Tracker Constraint',
203
- 'An active execution plan exists. Do not update currentPlan or taskUpdates. Track only decisions, questions, risks, artifacts, approvals, and agent notes.',
204
- ]
205
- : []),
206
- ].join('\n')
207
- }
208
-
209
- export function applyTrackedStateDelta(params: {
210
- existingState: WorkstreamState
211
- delta: WorkstreamStateDelta
212
- hasActiveExecutionPlan: boolean
213
- now: () => number
214
- }): WorkstreamState {
215
- const mergedState = mergeStateDelta(params.existingState, params.delta, params.now)
216
- if (!params.hasActiveExecutionPlan) {
217
- return mergedState
218
- }
219
-
220
- return { ...mergedState, currentPlan: null, tasks: [] }
221
- }
222
-
223
- export async function updateWorkstreamChangeTracker(params: {
224
- workstreamId: RecordIdRef
225
- title: string
226
- mode: 'direct' | 'group'
227
- coreType?: string
228
- visibleAgentId?: string
229
- hasActiveExecutionPlan: boolean
230
- previousSummary: string | null
231
- existingState: WorkstreamState | null
232
- userMessageText: string | null
233
- assistantMessages: TrackerMessage[]
234
- }): Promise<boolean> {
235
- const assistantMessages = params.assistantMessages
236
- .map((message) => ({
237
- label: toOptionalTrimmedString(message.label) ?? 'Assistant',
238
- text: truncateText(message.text.trim(), 2_400),
239
- }))
240
- .filter((message) => message.text.length > 0)
241
- .slice(0, 6)
242
-
243
- if (!toOptionalTrimmedString(params.userMessageText) && assistantMessages.length === 0) {
244
- return false
245
- }
246
-
247
- const existingState = params.existingState ?? createEmptyWorkstreamState()
248
-
249
- try {
250
- const output = await helperModelRuntime.generateHelperStructured({
251
- tag: 'workstream-change-tracker',
252
- createAgent: createWorkstreamTrackerAgent,
253
- schema: TrackerOutputSchema,
254
- maxOutputTokens: 1_400,
255
- textFallbackParser: parseTrackerTextFallback,
256
- messages: [
257
- {
258
- role: 'user',
259
- content: formatTrackerPrompt({
260
- title: params.title,
261
- mode: params.mode,
262
- ...(params.coreType ? { coreType: params.coreType } : {}),
263
- ...(params.visibleAgentId ? { visibleAgentId: params.visibleAgentId } : {}),
264
- hasActiveExecutionPlan: params.hasActiveExecutionPlan,
265
- previousSummary: params.previousSummary,
266
- existingState,
267
- userMessageText: toOptionalTrimmedString(params.userMessageText),
268
- assistantMessages,
269
- }),
270
- },
271
- ],
272
- })
273
-
274
- const sparseStateDelta = parseStructuredWorkstreamStateDelta(output.stateDelta)
275
- const nextState = applyTrackedStateDelta({
276
- existingState,
277
- delta: sparseStateDelta,
278
- hasActiveExecutionPlan: params.hasActiveExecutionPlan,
279
- now: () => Date.now(),
280
- })
281
- await workstreamService.persistChangeTracker(params.workstreamId, { chatSummary: output.summary, state: nextState })
282
- return true
283
- } catch (error) {
284
- const fallbackSummary = buildHeuristicTrackerSummary({
285
- previousSummary: params.previousSummary,
286
- userMessageText: toOptionalTrimmedString(params.userMessageText),
287
- assistantMessages,
288
- })
289
-
290
- if (!fallbackSummary) {
291
- aiLogger.warn`Workstream change tracker update failed: ${error}`
292
- return false
293
- }
294
-
295
- try {
296
- const nextState = applyTrackedStateDelta({
297
- existingState,
298
- delta: {},
299
- hasActiveExecutionPlan: params.hasActiveExecutionPlan,
300
- now: () => Date.now(),
301
- })
302
- await workstreamService.persistChangeTracker(params.workstreamId, {
303
- chatSummary: fallbackSummary,
304
- state: nextState,
305
- })
306
- aiLogger.info`Workstream change tracker used heuristic fallback after helper failure`
307
- return true
308
- } catch (persistError) {
309
- aiLogger.warn`Workstream change tracker update failed: ${error}; fallback_persist=${persistError}`
310
- return false
311
- }
312
- }
313
- }
@@ -1,58 +0,0 @@
1
- import { ToolLoopAgent } from 'ai'
2
-
3
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
4
- import {
5
- OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
6
- OPENROUTER_STRUCTURED_REASONING_MODEL_ID,
7
- } from '../config/model-constants'
8
- import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
9
- import { resolveHelperAgentOptions } from './helper-agent-options'
10
-
11
- const WORKSTREAM_TRACKER_PROMPT = `<agent-instructions>
12
- You are the **Workstream Tracker**.
13
-
14
- <task>
15
- Convert one completed workstream turn into:
16
- - a concise change-tracker summary for the right sidebar
17
- - a structured workstream state delta containing only the updates implied by this turn
18
- </task>
19
-
20
- <rules>
21
- - Use only the provided turn evidence and prior state.
22
- - Keep the summary short, concrete, and focused on what changed or what is blocked now.
23
- - Prefer durable tracker items over prose: tasks, decisions, questions, risks, artifacts, and agent notes.
24
- - Do not duplicate existing state unless this turn meaningfully changed it.
25
- - Never invent analytics, repository facts, customer signals, or business decisions that were not stated.
26
- </rules>
27
-
28
- <state-guidance>
29
- - Use taskUpdates for concrete next steps, ongoing work, completed work, or blocked work.
30
- - When the turn includes concrete tool work or operational execution, capture that work as taskUpdates even if it was
31
- completed in the same turn.
32
- - For website inspection or plugin refresh flows, prefer task titles that describe the executed step clearly, such as
33
- refreshing website intelligence, overwriting artifacts, or reviewing evidence gaps.
34
- - If an owner is not explicit, default the task owner to the visible lead agent for the workstream.
35
- - Use newDecisions only when a clear decision or tradeoff was made.
36
- - Use newQuestions only for unresolved questions that matter for progress.
37
- - Use newRisks only for concrete execution or business risks surfaced in this turn.
38
- - Use agentNote for the main contribution from the lead agent when it is worth retaining.
39
- - Keep the delta compact. A noisy tracker is worse than a sparse tracker.
40
- </state-guidance>
41
-
42
- <output>
43
- The caller enforces a structured schema.
44
- - Return every stateDelta field.
45
- - Use empty arrays for unchanged list fields.
46
- - Use null for unchanged nullable fields.
47
- - For currentPlan, use \`{"action":"unchanged","text":null}\` when nothing changed, \`{"action":"clear","text":null}\` to clear it, and \`{"action":"set","text":"..."}\` to replace it.
48
- </output>
49
- </agent-instructions>`
50
-
51
- export function createWorkstreamTrackerAgent(options: CreateHelperToolLoopAgentOptions) {
52
- return new ToolLoopAgent({
53
- id: 'workstream-tracker',
54
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_REASONING_MODEL_ID),
55
- providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
56
- ...resolveHelperAgentOptions(options, { instructions: WORKSTREAM_TRACKER_PROMPT }),
57
- })
58
- }