@lota-sdk/core 0.1.15 → 0.1.17

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 (159) hide show
  1. package/infrastructure/schema/00_identity.surql +0 -2
  2. package/infrastructure/schema/01_memory.surql +1 -1
  3. package/infrastructure/schema/02_execution_plan.surql +62 -1
  4. package/infrastructure/schema/03_learned_skill.surql +1 -1
  5. package/infrastructure/schema/06_playbook.surql +25 -0
  6. package/infrastructure/schema/07_institutional_memory.surql +13 -0
  7. package/infrastructure/schema/08_quality_metrics.surql +17 -0
  8. package/package.json +12 -8
  9. package/src/ai/definitions.ts +81 -3
  10. package/src/ai/embedding-cache.ts +2 -4
  11. package/src/ai/index.ts +0 -2
  12. package/src/bifrost/bifrost.ts +2 -7
  13. package/src/bifrost/cache-headers.ts +8 -0
  14. package/src/bifrost/index.ts +1 -0
  15. package/src/config/agent-defaults.ts +31 -21
  16. package/src/config/agent-types.ts +11 -0
  17. package/src/config/constants.ts +2 -14
  18. package/src/config/debug-logger.ts +5 -1
  19. package/src/config/index.ts +3 -0
  20. package/src/config/model-constants.ts +16 -34
  21. package/src/config/search.ts +1 -15
  22. package/src/create-runtime.ts +269 -178
  23. package/src/db/cursor-pagination.ts +3 -6
  24. package/src/db/index.ts +2 -0
  25. package/src/db/memory-store.helpers.ts +1 -3
  26. package/src/db/memory-store.rows.ts +7 -7
  27. package/src/db/memory-store.ts +14 -18
  28. package/src/db/memory.ts +13 -13
  29. package/src/db/schema-fingerprint.ts +1 -3
  30. package/src/db/service.ts +153 -79
  31. package/src/db/startup.ts +6 -10
  32. package/src/db/surreal-mutation.ts +43 -0
  33. package/src/db/tables.ts +7 -0
  34. package/src/db/workstream-message-row.ts +15 -0
  35. package/src/embeddings/provider.ts +1 -1
  36. package/src/queues/context-compaction.queue.ts +15 -46
  37. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  38. package/src/queues/document-processor.queue.ts +2 -4
  39. package/src/queues/index.ts +3 -0
  40. package/src/queues/memory-consolidation.queue.ts +16 -51
  41. package/src/queues/plan-scheduler.queue.ts +97 -0
  42. package/src/queues/post-chat-memory.queue.ts +20 -55
  43. package/src/queues/queue-factory.ts +100 -0
  44. package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
  45. package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
  46. package/src/queues/skill-extraction.queue.ts +15 -47
  47. package/src/queues/workstream-title-generation.queue.ts +15 -47
  48. package/src/redis/connection.ts +6 -0
  49. package/src/redis/index.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +1 -2
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +109 -35
  53. package/src/runtime/approval-continuation.ts +12 -6
  54. package/src/runtime/context-compaction-runtime.ts +1 -1
  55. package/src/runtime/context-compaction.ts +24 -64
  56. package/src/runtime/execution-plan.ts +22 -18
  57. package/src/runtime/graph-designer.ts +15 -0
  58. package/src/runtime/helper-model.ts +9 -197
  59. package/src/runtime/index.ts +3 -1
  60. package/src/runtime/llm-content.ts +1 -1
  61. package/src/runtime/memory-block.ts +9 -11
  62. package/src/runtime/memory-pipeline.ts +6 -9
  63. package/src/runtime/plugin-resolution.ts +35 -0
  64. package/src/runtime/plugin-types.ts +72 -0
  65. package/src/runtime/retrieval-adapters.ts +1 -1
  66. package/src/runtime/runtime-config.ts +111 -14
  67. package/src/runtime/runtime-extensions.ts +2 -3
  68. package/src/runtime/runtime-worker-registry.ts +6 -0
  69. package/src/runtime/social-chat.ts +752 -0
  70. package/src/runtime/team-consultation-orchestrator.ts +45 -32
  71. package/src/runtime/team-consultation-prompts.ts +11 -2
  72. package/src/runtime/title-helpers.ts +2 -4
  73. package/src/runtime/workstream-chat-helpers.ts +1 -1
  74. package/src/services/adaptive-playbook.service.ts +152 -0
  75. package/src/services/agent-executor.service.ts +292 -0
  76. package/src/services/artifact-provenance.service.ts +172 -0
  77. package/src/services/attachment.service.ts +6 -11
  78. package/src/services/context-compaction.service.ts +72 -55
  79. package/src/services/context-enrichment.service.ts +33 -0
  80. package/src/services/coordination-registry.service.ts +117 -0
  81. package/src/services/document-chunk.service.ts +2 -4
  82. package/src/services/domain-agent-executor.service.ts +71 -0
  83. package/src/services/execution-plan.service.ts +269 -50
  84. package/src/services/feedback-loop.service.ts +96 -0
  85. package/src/services/global-orchestrator.service.ts +148 -0
  86. package/src/services/index.ts +27 -0
  87. package/src/services/institutional-memory.service.ts +145 -0
  88. package/src/services/learned-skill.service.ts +24 -5
  89. package/src/services/memory-assessment.service.ts +3 -2
  90. package/src/services/memory-utils.ts +3 -8
  91. package/src/services/memory.service.ts +49 -61
  92. package/src/services/monitoring-window.service.ts +86 -0
  93. package/src/services/mutating-approval.service.ts +1 -1
  94. package/src/services/node-workspace.service.ts +155 -0
  95. package/src/services/notification.service.ts +39 -0
  96. package/src/services/organization-member.service.ts +11 -4
  97. package/src/services/organization.service.ts +5 -5
  98. package/src/services/ownership-dispatcher.service.ts +403 -0
  99. package/src/services/plan-approval.service.ts +1 -1
  100. package/src/services/plan-builder.service.ts +1 -0
  101. package/src/services/plan-checkpoint.service.ts +30 -2
  102. package/src/services/plan-compiler.service.ts +5 -0
  103. package/src/services/plan-coordination.service.ts +152 -0
  104. package/src/services/plan-cycle.service.ts +284 -0
  105. package/src/services/plan-deadline.service.ts +287 -0
  106. package/src/services/plan-executor.service.ts +384 -40
  107. package/src/services/plan-run.service.ts +41 -7
  108. package/src/services/plan-scheduler.service.ts +240 -0
  109. package/src/services/plan-template.service.ts +117 -0
  110. package/src/services/plan-validator.service.ts +84 -2
  111. package/src/services/plan-workspace.service.ts +83 -0
  112. package/src/services/playbook-registry.service.ts +67 -0
  113. package/src/services/plugin-executor.service.ts +103 -0
  114. package/src/services/quality-metrics.service.ts +132 -0
  115. package/src/services/recent-activity.service.ts +28 -34
  116. package/src/services/skill-resolver.service.ts +19 -0
  117. package/src/services/social-chat-history.service.ts +197 -0
  118. package/src/services/system-executor.service.ts +105 -0
  119. package/src/services/workstream-message.service.ts +13 -37
  120. package/src/services/workstream-plan-registry.service.ts +22 -0
  121. package/src/services/workstream-title.service.ts +3 -1
  122. package/src/services/workstream-turn-preparation.service.ts +34 -89
  123. package/src/services/workstream.service.ts +33 -55
  124. package/src/services/workstream.types.ts +9 -9
  125. package/src/services/write-intent-validator.service.ts +81 -0
  126. package/src/storage/attachment-parser.ts +1 -1
  127. package/src/storage/attachment-utils.ts +1 -1
  128. package/src/storage/generated-document-storage.service.ts +3 -2
  129. package/src/system-agents/context-compaction.agent.ts +2 -0
  130. package/src/system-agents/delegated-agent-factory.ts +5 -0
  131. package/src/system-agents/memory-reranker.agent.ts +4 -2
  132. package/src/system-agents/memory.agent.ts +2 -0
  133. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
  134. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
  135. package/src/system-agents/skill-extractor.agent.ts +2 -0
  136. package/src/system-agents/skill-manager.agent.ts +2 -0
  137. package/src/system-agents/title-generator.agent.ts +2 -0
  138. package/src/tools/execution-plan.tool.ts +17 -23
  139. package/src/tools/index.ts +0 -1
  140. package/src/tools/research-topic.tool.ts +2 -0
  141. package/src/tools/team-think.tool.ts +5 -6
  142. package/src/utils/async.ts +2 -1
  143. package/src/utils/date-time.ts +4 -32
  144. package/src/utils/env.ts +8 -0
  145. package/src/utils/errors.ts +42 -10
  146. package/src/utils/index.ts +9 -0
  147. package/src/utils/string.ts +114 -1
  148. package/src/workers/index.ts +1 -0
  149. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  150. package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
  151. package/src/workers/skill-extraction.runner.ts +26 -6
  152. package/src/workers/utils/file-section-chunker.ts +2 -1
  153. package/src/workers/utils/repo-structure-extractor.ts +2 -2
  154. package/src/workers/utils/repomix-file-sections.ts +2 -2
  155. package/src/workers/utils/sandbox-error.ts +11 -2
  156. package/src/workers/utils/workstream-message-query.ts +14 -25
  157. package/src/workers/worker-utils.ts +2 -2
  158. package/src/runtime/workstream-routing-policy.ts +0 -267
  159. package/src/tools/log-hello-world.tool.ts +0 -17
@@ -0,0 +1,105 @@
1
+ import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlanNodeOwner } from '@lota-sdk/shared'
2
+
3
+ import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
4
+ import { getRuntimeConfig } from '../runtime/runtime-config'
5
+ import type { PlanValidationIssueInput } from './plan-validator.service'
6
+
7
+ const BUILT_IN_SYSTEM_EXECUTORS = Object.freeze({
8
+ 'plan-runtime': {
9
+ supportedOperations: ['echo-input'],
10
+ async executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult> {
11
+ return { structuredOutput: structuredClone(params.inputs), artifacts: [] }
12
+ },
13
+ } satisfies SystemNodeExecutor,
14
+ })
15
+
16
+ export function getBuiltInSystemExecutors(): Record<string, SystemNodeExecutor> {
17
+ return Object.fromEntries(Object.entries(BUILT_IN_SYSTEM_EXECUTORS))
18
+ }
19
+
20
+ function getSystemExecutors() {
21
+ return (getRuntimeConfig().systemExecutors ?? getBuiltInSystemExecutors()) as Record<
22
+ string,
23
+ SystemNodeExecutor | undefined
24
+ >
25
+ }
26
+
27
+ function buildSystemExecutionParams(params: {
28
+ owner: SystemPlanNodeOwner
29
+ nodeSpec: PlanNodeSpec
30
+ resolvedInput: Record<string, unknown>
31
+ context: OwnershipDispatchContext
32
+ }): PluginNodeExecutionParams {
33
+ return {
34
+ operation: params.owner.operation,
35
+ nodeSpec: params.nodeSpec,
36
+ inputs: params.resolvedInput,
37
+ context: {
38
+ organizationId: params.context.organizationId,
39
+ workstreamId: params.context.workstreamId,
40
+ planId: params.context.planId,
41
+ nodeId: params.context.nodeId,
42
+ ...(params.context.userId ? { userId: params.context.userId } : {}),
43
+ ...(params.context.userName ? { userName: params.context.userName } : {}),
44
+ },
45
+ }
46
+ }
47
+
48
+ class SystemExecutorService {
49
+ validateOwner(owner: SystemPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
50
+ const executor = getSystemExecutors()[owner.ref]
51
+ if (!executor) {
52
+ return [
53
+ {
54
+ severity: 'blocking',
55
+ code: 'system_executor_missing',
56
+ message: `Node "${nodeId}" references unknown system executor "${owner.ref}".`,
57
+ nodeId,
58
+ detail: { systemRef: owner.ref },
59
+ },
60
+ ]
61
+ }
62
+
63
+ if (!executor.supportedOperations.includes(owner.operation)) {
64
+ return [
65
+ {
66
+ severity: 'blocking',
67
+ code: 'system_operation_missing',
68
+ message: `System executor "${owner.ref}" does not support operation "${owner.operation}".`,
69
+ nodeId,
70
+ detail: { systemRef: owner.ref, operation: owner.operation },
71
+ },
72
+ ]
73
+ }
74
+
75
+ return []
76
+ }
77
+
78
+ async executeNode(params: {
79
+ nodeSpec: PlanNodeSpec
80
+ resolvedInput: Record<string, unknown>
81
+ context: OwnershipDispatchContext
82
+ }): Promise<PlanNodeResult> {
83
+ if (params.nodeSpec.owner.executorType !== 'system') {
84
+ throw new Error(`SystemExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
85
+ }
86
+
87
+ const executor = getSystemExecutors()[params.nodeSpec.owner.ref]
88
+ if (!executor || !executor.supportedOperations.includes(params.nodeSpec.owner.operation)) {
89
+ throw new Error(
90
+ `System executor ${params.nodeSpec.owner.ref}.${params.nodeSpec.owner.operation} is not registered.`,
91
+ )
92
+ }
93
+
94
+ return executor.executeNode(
95
+ buildSystemExecutionParams({
96
+ owner: params.nodeSpec.owner,
97
+ nodeSpec: params.nodeSpec,
98
+ resolvedInput: params.resolvedInput,
99
+ context: params.context,
100
+ }),
101
+ )
102
+ }
103
+ }
104
+
105
+ export const systemExecutorService = new SystemExecutorService()
@@ -1,6 +1,4 @@
1
- import { createHash } from 'node:crypto'
2
-
3
- import { parseRowMetadata, toTimestamp, withCreatedAtMetadata } from '@lota-sdk/shared'
1
+ import { parseRowMetadata, recordIdSchema, requireTimestamp, withCreatedAtMetadata } from '@lota-sdk/shared'
4
2
  import type { ChatMessage } from '@lota-sdk/shared'
5
3
  import { RecordId, surql } from 'surrealdb'
6
4
  import { z } from 'zod'
@@ -12,24 +10,10 @@ import { recordIdToString } from '../db/record-id'
12
10
  import type { RecordIdRef } from '../db/record-id'
13
11
  import { databaseService } from '../db/service'
14
12
  import { TABLES } from '../db/tables'
13
+ import { WorkstreamMessageRowSchema } from '../db/workstream-message-row'
14
+ import type { WorkstreamMessageRow } from '../db/workstream-message-row'
15
15
 
16
- const WorkstreamMessageRowSchema = z.object({
17
- id: z.unknown(),
18
- workstreamId: z.unknown(),
19
- messageId: z.string(),
20
- role: z.enum(['system', 'user', 'assistant']),
21
- parts: z.array(z.record(z.string(), z.unknown())).optional(),
22
- metadata: z.record(z.string(), z.unknown()).nullish(),
23
- createdAt: z.union([z.date(), z.string(), z.number()]),
24
- updatedAt: z.union([z.date(), z.string(), z.number()]).optional(),
25
- })
26
-
27
- type WorkstreamMessageRow = z.infer<typeof WorkstreamMessageRowSchema>
28
-
29
- const WorkstreamMessageExistingRowSchema = z.object({
30
- id: z.unknown(),
31
- createdAt: z.union([z.date(), z.string(), z.number()]),
32
- })
16
+ const WorkstreamMessageExistingRowSchema = z.object({ id: recordIdSchema, createdAt: z.coerce.date() })
33
17
 
34
18
  function toMessageId(value: string | RecordIdRef): string {
35
19
  return recordIdToString(value, TABLES.WORKSTREAM_MESSAGE)
@@ -44,15 +28,15 @@ function toMessageId(value: string | RecordIdRef): string {
44
28
  */
45
29
  function toWorkstreamMessageRowId(workstreamId: RecordIdRef, messageId: string): RecordId {
46
30
  const workstreamStr = recordIdToString(workstreamId, TABLES.WORKSTREAM)
47
- const digest = createHash('sha256').update(`${workstreamStr}\0${messageId}`).digest('hex').slice(0, 32)
31
+ const digest = new Bun.CryptoHasher('sha256').update(`${workstreamStr}\0${messageId}`).digest('hex').slice(0, 32)
48
32
  return new RecordId(TABLES.WORKSTREAM_MESSAGE, digest)
49
33
  }
50
34
 
51
35
  function toChatMessage(row: WorkstreamMessageRow): ChatMessage {
52
- const rowCreatedAt = toTimestamp(row.createdAt) ?? Date.now()
36
+ const rowCreatedAt = requireTimestamp(row.createdAt)
53
37
  const metadata = withCreatedAtMetadata(parseRowMetadata(row.metadata), rowCreatedAt)
54
38
 
55
- return { id: row.messageId, role: row.role, parts: (row.parts ?? []) as ChatMessage['parts'], metadata }
39
+ return { id: row.messageId, role: row.role, parts: row.parts as ChatMessage['parts'], metadata }
56
40
  }
57
41
 
58
42
  const workstreamPaginationConfig: CursorPaginationConfig = {
@@ -102,9 +86,7 @@ class WorkstreamMessageService {
102
86
  WorkstreamMessageExistingRowSchema,
103
87
  )
104
88
  const persistedCreatedAt =
105
- existingRow === null
106
- ? (toTimestamp(message.metadata?.createdAt) ?? Date.now())
107
- : (toTimestamp(existingRow.createdAt) ?? Date.now())
89
+ existingRow === null ? requireTimestamp(message.metadata?.createdAt) : requireTimestamp(existingRow.createdAt)
108
90
  const metadata = withCreatedAtMetadata({ ...message.metadata, createdAt: persistedCreatedAt })
109
91
 
110
92
  await databaseService.upsert(
@@ -116,9 +98,7 @@ class WorkstreamMessageService {
116
98
  role,
117
99
  parts,
118
100
  metadata,
119
- createdAt: existingRow
120
- ? new Date(toTimestamp(existingRow.createdAt) ?? Date.now())
121
- : new Date(persistedCreatedAt),
101
+ createdAt: existingRow ? existingRow.createdAt : new Date(persistedCreatedAt),
122
102
  },
123
103
  WorkstreamMessageRowSchema,
124
104
  { mutation: 'content' },
@@ -152,7 +132,7 @@ class WorkstreamMessageService {
152
132
  async listMessagesAfterCursor(workstreamId: RecordIdRef, afterMessageId?: string): Promise<ChatMessage[]> {
153
133
  const cursorMessageId = afterMessageId?.trim()
154
134
  if (!cursorMessageId) {
155
- return await this.listMessages(workstreamId)
135
+ return this.listMessages(workstreamId)
156
136
  }
157
137
 
158
138
  const cursorRow = await databaseService.findOne(
@@ -165,7 +145,7 @@ class WorkstreamMessageService {
165
145
  throw new Error(`Workstream cursor message not found: ${cursorMessageId}`)
166
146
  }
167
147
 
168
- const cursorCreatedAt = new Date(toTimestamp(cursorRow.createdAt) ?? Date.now())
148
+ const cursorCreatedAt = cursorRow.createdAt
169
149
  const cursorId = toWorkstreamMessageRowId(workstreamId, cursorMessageId)
170
150
  const rows = await databaseService.query<unknown>(surql`
171
151
  SELECT * FROM workstreamMessage
@@ -209,7 +189,7 @@ class WorkstreamMessageService {
209
189
  .map((message) => ({
210
190
  id: message.id,
211
191
  role: message.role as 'user' | 'assistant',
212
- createdAt: new Date(toTimestamp(message.metadata?.createdAt) ?? Date.now()).toISOString(),
192
+ createdAt: new Date(requireTimestamp(message.metadata?.createdAt)).toISOString(),
213
193
  content: message.parts
214
194
  .flatMap((part) => (part.type === 'text' && typeof part.text === 'string' ? [part.text] : []))
215
195
  .join('\n')
@@ -245,7 +225,7 @@ class WorkstreamMessageService {
245
225
  id: toMessageId(params.messageId),
246
226
  role: 'assistant',
247
227
  parts: params.parts,
248
- metadata: withCreatedAtMetadata(params.metadata),
228
+ metadata: withCreatedAtMetadata(params.metadata, Date.now()),
249
229
  }
250
230
 
251
231
  await this.upsertMessages({ workstreamId: params.workstreamId, messages: [message] })
@@ -283,10 +263,6 @@ class WorkstreamMessageService {
283
263
  ],
284
264
  })
285
265
  }
286
-
287
- async listAllMessages(workstreamId: RecordIdRef): Promise<ChatMessage[]> {
288
- return await this.listMessages(workstreamId)
289
- }
290
266
  }
291
267
 
292
268
  export const workstreamMessageService = new WorkstreamMessageService()
@@ -0,0 +1,22 @@
1
+ import type { PlanRunRecord, SerializableExecutionPlan } from '@lota-sdk/shared'
2
+
3
+ import type { RecordIdInput } from '../db/record-id'
4
+ import { planRunService } from './plan-run.service'
5
+
6
+ class WorkstreamPlanRegistryService {
7
+ async listActiveRuns(workstreamId: RecordIdInput): Promise<PlanRunRecord[]> {
8
+ return planRunService.getActiveRunRecords(workstreamId)
9
+ }
10
+
11
+ async countActiveRuns(workstreamId: RecordIdInput): Promise<number> {
12
+ const runs = await this.listActiveRuns(workstreamId)
13
+ return runs.length
14
+ }
15
+
16
+ async listActivePlans(workstreamId: RecordIdInput): Promise<SerializableExecutionPlan[]> {
17
+ const runs = await this.listActiveRuns(workstreamId)
18
+ return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run)))
19
+ }
20
+ }
21
+
22
+ export const workstreamPlanRegistryService = new WorkstreamPlanRegistryService()
@@ -10,6 +10,8 @@ import {
10
10
  } from '../system-agents/title-generator.agent'
11
11
  import { workstreamService } from './workstream.service'
12
12
 
13
+ const WORKSTREAM_TITLE_TIMEOUT_MS = 5_000
14
+
13
15
  class WorkstreamTitleService {
14
16
  helperRuntime = createHelperModelRuntime()
15
17
 
@@ -21,7 +23,7 @@ class WorkstreamTitleService {
21
23
  tag: 'workstream-title',
22
24
  createAgent: createWorkstreamTitleGeneratorAgent,
23
25
  defaultSystemPrompt: WORKSTREAM_TITLE_GENERATOR_PROMPT,
24
- timeoutMs: 30_000,
26
+ timeoutMs: WORKSTREAM_TITLE_TIMEOUT_MS,
25
27
  messages: [{ role: 'user', content: sourceText }],
26
28
  }),
27
29
  )
@@ -1,4 +1,5 @@
1
1
  import {
2
+ WORKSTREAM,
2
3
  baseChatMessageSchema,
3
4
  CONSULT_SPECIALIST_TOOL_NAME,
4
5
  CONSULT_TEAM_TOOL_NAME,
@@ -10,7 +11,7 @@ import {
10
11
  } from '@lota-sdk/shared'
11
12
  import type { ChatMessage, MessageMetadata } from '@lota-sdk/shared'
12
13
  import { convertToModelMessages, readUIMessageStream, stepCountIs, tool as createTool, validateUIMessages } from 'ai'
13
- import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
14
+ import type { PrepareStepFunction, StopCondition, ToolLoopAgent, ToolSet, UIMessageStreamWriter } from 'ai'
14
15
  import type { z } from 'zod'
15
16
 
16
17
  import type { CoreWorkstreamProfile } from '../config/agent-defaults'
@@ -21,7 +22,6 @@ import {
21
22
  getLeadAgentId,
22
23
  getCoreWorkstreamProfile,
23
24
  getAgentRuntimeConfig,
24
- pluginRuntime,
25
25
  } from '../config/agent-defaults'
26
26
  import { lotaDebugLogger } from '../config/debug-logger'
27
27
  import { aiLogger } from '../config/logger'
@@ -55,6 +55,7 @@ import {
55
55
  shouldEnqueueOnboardingPostChatMemory,
56
56
  shouldEnqueueRegularDigestForWorkstream,
57
57
  } from '../runtime/memory-digest-policy'
58
+ import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
58
59
  import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/runtime-extensions'
59
60
  import { shouldEnqueueSkillExtraction } from '../runtime/skill-extraction-policy'
60
61
  import { finalizeTurnRun } from '../runtime/turn-lifecycle'
@@ -68,11 +69,6 @@ import {
68
69
  toHistoryMessages,
69
70
  toOptionalTrimmedString,
70
71
  } from '../runtime/workstream-chat-helpers'
71
- import {
72
- classifyHighImpactResponse,
73
- classifyPolicyClasses,
74
- resolveReasoningProfile,
75
- } from '../runtime/workstream-routing-policy'
76
72
  import type { WorkstreamState } from '../runtime/workstream-state'
77
73
  import { chatRunRegistry } from '../services/chat-run-registry.service'
78
74
  import type { NormalizedWorkstream, WorkstreamRecord } from '../services/workstream.types'
@@ -86,12 +82,11 @@ import { contextCompactionRuntime } from './context-compaction-runtime.singleton
86
82
  import { executionPlanService } from './execution-plan.service'
87
83
  import { learnedSkillService } from './learned-skill.service'
88
84
  import { memoryService } from './memory.service'
85
+ import { planRunService } from './plan-run.service'
89
86
  import { recentActivityService } from './recent-activity.service'
90
87
  import { workstreamMessageService } from './workstream-message.service'
91
88
  import { workstreamService } from './workstream.service'
92
89
 
93
- type AgentRuntimeConfig = Record<string, unknown>
94
- type AgentFactory = Record<string, (...args: unknown[]) => Record<string, (...args: unknown[]) => unknown>>
95
90
  type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
96
91
 
97
92
  interface UIMessageStreamResult {
@@ -106,45 +101,8 @@ function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
106
101
  )
107
102
  }
108
103
 
109
- function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
110
- let current: unknown = pluginRuntime
111
- let owner: unknown = undefined
112
- for (const key of path) {
113
- if (current === null || current === undefined || typeof current !== 'object') return undefined
114
- owner = current
115
- current = (current as Record<string, unknown>)[key]
116
- }
117
- if (typeof current !== 'function') {
118
- return undefined
119
- }
120
-
121
- return owner && typeof owner === 'object'
122
- ? (current as (...args: unknown[]) => unknown).bind(owner)
123
- : (current as (...args: unknown[]) => unknown)
124
- }
125
-
126
- async function buildIndexedRepositoriesContext(
127
- workspaceId: string,
128
- ): Promise<{ provideRepoTool: boolean; defaultSectionsByAgent: Record<string, unknown>; context: string }> {
129
- const buildContext = getRuntimeAdapters().workstream?.buildIndexedRepositoriesContext
130
- if (!buildContext) {
131
- return { provideRepoTool: false, defaultSectionsByAgent: {}, context: '' }
132
- }
133
-
134
- const context = await buildContext(workspaceId)
135
- return {
136
- provideRepoTool: context.provideRepoTool,
137
- defaultSectionsByAgent: context.defaultSectionsByAgent,
138
- context: context.context ?? '',
139
- }
140
- }
141
-
142
104
  const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
143
105
 
144
- function parsePersistedWorkstreamState(value: unknown): WorkstreamState | null {
145
- return parseWorkstreamState(value)
146
- }
147
-
148
106
  function stripExecutionPlanFieldsFromWorkstreamState(
149
107
  state: WorkstreamState | null | undefined,
150
108
  hasExecutionPlan: boolean,
@@ -155,7 +113,7 @@ function stripExecutionPlanFieldsFromWorkstreamState(
155
113
  }
156
114
 
157
115
  async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
158
- return await waitForCompactionIfNeeded({
116
+ return waitForCompactionIfNeeded({
159
117
  entityId: recordIdToString(workstreamId, TABLES.WORKSTREAM),
160
118
  entityLabel: 'Workstream',
161
119
  loadEntity: () => workstreamService.getById(workstreamId),
@@ -311,7 +269,6 @@ interface StreamAgentResponseContext {
311
269
  onboardingActive: boolean
312
270
  linearInstalled: boolean
313
271
  githubInstalled: boolean
314
- reasoningProfileName: string
315
272
  buildContextResult: Record<string, unknown> | null
316
273
  getExecutionPlanInstructionSections: () => Promise<string[] | undefined>
317
274
  getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
@@ -361,7 +318,6 @@ async function streamAgentResponse(
361
318
  onboardingActive: ctx.onboardingActive,
362
319
  linearInstalled: ctx.linearInstalled,
363
320
  githubInstalled: ctx.githubInstalled,
364
- reasoningProfile: ctx.reasoningProfileName,
365
321
  skills: streamParams.skills,
366
322
  additionalInstructionSections: streamParams.additionalInstructionSections,
367
323
  context: ctx.buildContextResult,
@@ -382,7 +338,6 @@ async function streamAgentResponse(
382
338
  skills: streamParams.skills,
383
339
  onboardingActive: ctx.onboardingActive,
384
340
  linearInstalled: ctx.linearInstalled,
385
- reasoningProfile: ctx.reasoningProfileName,
386
341
  systemWorkspaceDetails: ctx.promptContext.systemWorkspaceDetails,
387
342
  preSeededMemoriesSection,
388
343
  retrievedKnowledgeSection: ctx.retrievedKnowledgeSection,
@@ -397,18 +352,18 @@ async function streamAgentResponse(
397
352
  optionalInstructionSection(agentResolution?.extraInstructions),
398
353
  ),
399
354
  context: ctx.buildContextResult,
400
- }) as AgentRuntimeConfig
355
+ }) as Record<string, unknown>
401
356
  agentTimer.step('build-agent-config')
402
357
  const modelMessages = await convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true })
403
358
  agentTimer.step('convert-model-messages')
404
- const agent = (createAgent as unknown as AgentFactory)[config.id as string]({
359
+ const agent = createAgent[config.id as string]({
405
360
  mode: streamParams.mode,
406
361
  tools: streamParams.tools,
407
362
  extraInstructions: config.extraInstructions,
408
363
  stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
409
364
  streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
410
365
  prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
411
- })
366
+ }) as ToolLoopAgent<never, ToolSet>
412
367
  const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
413
368
  agentTimer.step('agent-construction')
414
369
 
@@ -443,7 +398,7 @@ async function streamAgentResponse(
443
398
  sendSources: true,
444
399
  messageMetadata: createAgentMessageMetadata({ agentId: resolvedAgentId, agentName: config.displayName as string }),
445
400
  onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
446
- responseMessage = withMessageCreatedAt(finishedResponseMessage)
401
+ responseMessage = withMessageCreatedAt(finishedResponseMessage, Date.now())
447
402
  resolveFinishedStream()
448
403
  },
449
404
  }) as ReadableStream<ChatStreamChunk>
@@ -474,7 +429,7 @@ async function streamAgentResponse(
474
429
  }
475
430
 
476
431
  for (const toolError of collectToolOutputErrors({ responseMessage: responseMessage })) {
477
- aiLogger.warn`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
432
+ aiLogger.error`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
478
433
  }
479
434
 
480
435
  return responseMessage
@@ -657,7 +612,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
657
612
  const shouldProcessPostRunSideEffects =
658
613
  params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage
659
614
  if (params.kind === 'userTurn') {
660
- inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage))
615
+ inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage, Date.now()))
661
616
  if (inputMessage.role !== 'user') {
662
617
  throw new WorkstreamTurnError('Only user messages can be submitted to the workstream runtime.', 400)
663
618
  }
@@ -691,7 +646,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
691
646
  timer.step('persist-approval-message')
692
647
  }
693
648
 
694
- const initialWorkstreamState = parsePersistedWorkstreamState(workstreamRecord.state)
649
+ const initialWorkstreamState = parseWorkstreamState(workstreamRecord.state)
695
650
  const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
696
651
  const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
697
652
  workstreamRef,
@@ -703,10 +658,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
703
658
  if (inputMessage) {
704
659
  userMessage = {
705
660
  ...inputMessage,
706
- id: inputMessage.id || Bun.randomUUIDv7(),
661
+ id: inputMessage.id,
707
662
  role: 'user',
708
663
  parts: inputMessage.parts,
709
- metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) ?? Date.now() },
664
+ metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) },
710
665
  }
711
666
  }
712
667
 
@@ -758,6 +713,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
758
713
  workstream.mode === 'group' &&
759
714
  !workstream.core &&
760
715
  workstreamRecord.nameGenerated !== true &&
716
+ workstreamRecord.title === WORKSTREAM.DEFAULT_TITLE &&
761
717
  messageText.length > 0
762
718
  ) {
763
719
  void safeEnqueue(
@@ -789,15 +745,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
789
745
  'getInstallationForOrganization',
790
746
  ])
791
747
 
792
- const highImpactAssessment = classifyHighImpactResponse({ message: messageText })
793
- const policyAssessment = classifyPolicyClasses({ message: messageText })
794
- const reasoningProfile = resolveReasoningProfile({
795
- message: messageText,
796
- forceDeep: highImpactAssessment.classes.length > 0 || policyAssessment.classes.length > 0,
797
- explicitProfile: onboardingActive ? 'standard' : undefined,
798
- })
799
- timer.step('reasoning-classification')
800
-
801
748
  const [linearInstallation, githubInstallation, indexedRepoContext, recentDomainEvents, promptSummary] =
802
749
  await Promise.all([
803
750
  getLinearInstallationByOrgId ? (getLinearInstallationByOrgId(orgRef) as Promise<unknown>) : Promise.resolve(null),
@@ -896,18 +843,21 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
896
843
  let workstreamState = initialWorkstreamState
897
844
  const executionPlanInstructionSectionCache = createExecutionPlanInstructionSectionCache({
898
845
  disabled: onboardingActive,
899
- loadPlan: async () => await executionPlanService.getActivePlanForWorkstream(workstreamRef),
846
+ loadPlans: async () => {
847
+ const runs = await planRunService.getActiveRunRecords(workstreamRef)
848
+ return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run)))
849
+ },
900
850
  })
901
- const getExecutionPlan = async () => await executionPlanInstructionSectionCache.getPlan()
851
+ const getExecutionPlans = async () => await executionPlanInstructionSectionCache.getPlans()
902
852
  const getExecutionPlanInstructionSections = async (): Promise<string[] | undefined> =>
903
853
  await executionPlanInstructionSectionCache.getSections()
904
854
  const invalidateExecutionPlanInstructionSections = () => {
905
855
  executionPlanInstructionSectionCache.invalidate()
906
856
  }
907
857
  const getWorkstreamStateSection = async (): Promise<string | undefined> => {
908
- const executionPlan = await getExecutionPlan()
858
+ const executionPlans = await getExecutionPlans()
909
859
  return contextCompactionRuntime.formatWorkstreamStateForPrompt(
910
- stripExecutionPlanFieldsFromWorkstreamState(workstreamState, Boolean(executionPlan)),
860
+ stripExecutionPlanFieldsFromWorkstreamState(workstreamState, executionPlans.length > 0),
911
861
  )
912
862
  }
913
863
  const respondedBy = recordIdToString(userRef, TABLES.USER)
@@ -1001,9 +951,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1001
951
  const buildAgentMetadataPatch = (agentId: string, agentName: string): NonNullable<MessageMetadata> => ({
1002
952
  agentId,
1003
953
  agentName,
1004
- reasoningProfile: reasoningProfile.name,
1005
- highImpactClasses: highImpactAssessment.classes,
1006
- policyClasses: policyAssessment.classes,
1007
954
  semanticTerminationReason: 'none',
1008
955
  })
1009
956
 
@@ -1018,10 +965,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1018
965
  })
1019
966
 
1020
967
  const commitAssistantResponse = async (response: ChatMessage, agentId: string, agentName: string) => {
1021
- const committed = withMessageCreatedAt({
1022
- ...response,
1023
- metadata: { ...response.metadata, ...buildAgentMetadataPatch(agentId, agentName) },
1024
- })
968
+ const committed = withMessageCreatedAt(
969
+ { ...response, metadata: { ...response.metadata, ...buildAgentMetadataPatch(agentId, agentName) } },
970
+ Date.now(),
971
+ )
1025
972
 
1026
973
  await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [committed] })
1027
974
  currentMessages = upsertChatHistoryMessage(currentMessages, committed)
@@ -1040,7 +987,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1040
987
  onboardingActive,
1041
988
  linearInstalled,
1042
989
  githubInstalled,
1043
- reasoningProfileName: reasoningProfile.name,
1044
990
  buildContextResult,
1045
991
  getExecutionPlanInstructionSections,
1046
992
  getPreSeededMemoriesSection,
@@ -1063,6 +1009,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1063
1009
  }): Promise<ChatMessage> => {
1064
1010
  const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
1065
1011
  let runMemoryBlock = memoryBlock
1012
+ const includeExecutionPlanTools = runParams.mode !== 'fixedWorkstreamMode' && !onboardingActive
1066
1013
  const tools: ToolSet = {
1067
1014
  ...((await buildAgentTools({
1068
1015
  agentId: runParams.agentId,
@@ -1084,7 +1031,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1084
1031
  runMemoryBlock = value
1085
1032
  },
1086
1033
  availableUploads: listReadableUploads(runParams.extraMessages),
1087
- includeExecutionPlanTools: runParams.mode !== 'fixedWorkstreamMode',
1034
+ includeExecutionPlanTools,
1088
1035
  onExecutionPlanChanged: invalidateExecutionPlanInstructionSections,
1089
1036
  context: buildContextResult,
1090
1037
  })) as ToolSet),
@@ -1107,7 +1054,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1107
1054
  visibleTimer.step('stream-agent-response')
1108
1055
  memoryBlock = runMemoryBlock
1109
1056
 
1110
- return await commitAssistantResponse(
1057
+ return commitAssistantResponse(
1111
1058
  responseMessage,
1112
1059
  runParams.agentId,
1113
1060
  agentDisplayNames[runParams.agentId] ?? runParams.agentId,
@@ -1169,7 +1116,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1169
1116
  mode: 'fixedWorkstreamMode',
1170
1117
  onboardingActive,
1171
1118
  linearInstalled,
1172
- reasoningProfile: reasoningProfile.name,
1173
1119
  systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
1174
1120
  preSeededMemoriesSection: specialistPreSeededMemories,
1175
1121
  retrievedKnowledgeSection,
@@ -1182,14 +1128,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1182
1128
  hookInstructionSections,
1183
1129
  ),
1184
1130
  context: buildContextResult,
1185
- }) as AgentRuntimeConfig
1131
+ }) as Record<string, unknown>
1186
1132
  const observer = createObserver(agentId)
1187
- const agent = (createAgent as unknown as AgentFactory)[specialistConfig.id as string]({
1133
+ const agent = createAgent[specialistConfig.id as string]({
1188
1134
  mode: 'fixedWorkstreamMode',
1189
1135
  tools: { ...(specialistTools as ToolSet), ...toolProviders },
1190
1136
  extraInstructions: specialistConfig.extraInstructions,
1191
1137
  stopWhen: [stepCountIs(specialistConfig.maxSteps as number)],
1192
- })
1138
+ }) as ToolLoopAgent<never, ToolSet>
1193
1139
  const modelMessages = await convertToModelMessages(buildRunInputMessages([specialistTaskMessage]), {
1194
1140
  ignoreIncompleteToolCalls: true,
1195
1141
  })
@@ -1225,7 +1171,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1225
1171
  }),
1226
1172
  }) as ReadableStream<never>,
1227
1173
  })) {
1228
- finalMessage = withMessageCreatedAt(message)
1174
+ finalMessage = withMessageCreatedAt(message, Date.now())
1229
1175
  yield finalMessage
1230
1176
  }
1231
1177
 
@@ -1266,7 +1212,6 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1266
1212
  availableUploads: listReadableUploads(),
1267
1213
  provideRepoTool: indexedRepoContext.provideRepoTool,
1268
1214
  defaultRepoSectionsByAgent: indexedRepoContext.defaultSectionsByAgent as never,
1269
- reasoningProfile: reasoningProfile.name,
1270
1215
  systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
1271
1216
  getPreSeededMemoriesSection,
1272
1217
  retrievedKnowledgeSection,
@@ -1301,7 +1246,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
1301
1246
  } finally {
1302
1247
  try {
1303
1248
  const latestWorkstreamRecord = await workstreamService.getById(workstreamRef)
1304
- const latestPersistedState = parsePersistedWorkstreamState(latestWorkstreamRecord.state)
1249
+ const latestPersistedState = parseWorkstreamState(latestWorkstreamRecord.state)
1305
1250
 
1306
1251
  await finalizeTurnRun({
1307
1252
  serverRunId,