@lota-sdk/core 0.1.14 → 0.1.16

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 (174) 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 +9 -8
  9. package/src/ai/definitions.ts +80 -2
  10. package/src/ai/embedding-cache.ts +7 -6
  11. package/src/ai/index.ts +0 -1
  12. package/src/bifrost/bifrost.ts +14 -14
  13. package/src/config/agent-defaults.ts +32 -22
  14. package/src/config/agent-types.ts +11 -0
  15. package/src/config/constants.ts +2 -14
  16. package/src/config/debug-logger.ts +5 -1
  17. package/src/config/index.ts +3 -0
  18. package/src/config/logger.ts +7 -9
  19. package/src/config/model-constants.ts +16 -34
  20. package/src/config/search.ts +1 -15
  21. package/src/create-runtime.ts +453 -0
  22. package/src/db/cursor-pagination.ts +3 -6
  23. package/src/db/index.ts +2 -0
  24. package/src/db/memory-store.rows.ts +7 -7
  25. package/src/db/memory-store.ts +24 -24
  26. package/src/db/memory.ts +18 -16
  27. package/src/db/schema-fingerprint.ts +1 -0
  28. package/src/db/service.ts +193 -122
  29. package/src/db/startup.ts +9 -13
  30. package/src/db/surreal-mutation.ts +43 -0
  31. package/src/db/tables.ts +7 -0
  32. package/src/db/workstream-message-row.ts +15 -0
  33. package/src/embeddings/provider.ts +1 -1
  34. package/src/index.ts +1 -1
  35. package/src/queues/context-compaction.queue.ts +17 -52
  36. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  37. package/src/queues/document-processor.queue.ts +7 -7
  38. package/src/queues/index.ts +3 -0
  39. package/src/queues/memory-consolidation.queue.ts +18 -54
  40. package/src/queues/plan-scheduler.queue.ts +97 -0
  41. package/src/queues/post-chat-memory.queue.ts +15 -60
  42. package/src/queues/queue-factory.ts +100 -0
  43. package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
  44. package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
  45. package/src/queues/skill-extraction.queue.ts +15 -50
  46. package/src/queues/workstream-title-generation.queue.ts +15 -51
  47. package/src/redis/connection.ts +12 -3
  48. package/src/redis/index.ts +2 -1
  49. package/src/redis/org-memory-lock.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +41 -8
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +106 -21
  53. package/src/runtime/agent-stream-helpers.ts +2 -1
  54. package/src/runtime/approval-continuation.ts +12 -6
  55. package/src/runtime/context-compaction-constants.ts +1 -1
  56. package/src/runtime/context-compaction-runtime.ts +7 -5
  57. package/src/runtime/context-compaction.ts +40 -97
  58. package/src/runtime/execution-plan.ts +23 -19
  59. package/src/runtime/graph-designer.ts +15 -0
  60. package/src/runtime/helper-model.ts +10 -196
  61. package/src/runtime/index.ts +14 -1
  62. package/src/runtime/llm-content.ts +1 -1
  63. package/src/runtime/memory-block.ts +11 -12
  64. package/src/runtime/memory-pipeline.ts +26 -10
  65. package/src/runtime/plugin-resolution.ts +35 -0
  66. package/src/runtime/plugin-types.ts +73 -1
  67. package/src/runtime/retrieval-adapters.ts +1 -1
  68. package/src/runtime/runtime-config.ts +25 -12
  69. package/src/runtime/runtime-extensions.ts +91 -15
  70. package/src/runtime/runtime-worker-registry.ts +6 -0
  71. package/src/runtime/team-consultation-orchestrator.ts +45 -28
  72. package/src/runtime/team-consultation-prompts.ts +11 -2
  73. package/src/runtime/title-helpers.ts +11 -4
  74. package/src/runtime/workstream-chat-helpers.ts +6 -7
  75. package/src/runtime/workstream-routing-policy.ts +0 -30
  76. package/src/runtime/workstream-state.ts +17 -7
  77. package/src/services/adaptive-playbook.service.ts +152 -0
  78. package/src/services/agent-executor.service.ts +293 -0
  79. package/src/services/artifact-provenance.service.ts +172 -0
  80. package/src/services/attachment.service.ts +7 -12
  81. package/src/services/context-compaction.service.ts +75 -58
  82. package/src/services/context-enrichment.service.ts +33 -0
  83. package/src/services/coordination-registry.service.ts +117 -0
  84. package/src/services/document-chunk.service.ts +38 -33
  85. package/src/services/domain-agent-executor.service.ts +71 -0
  86. package/src/services/execution-plan.service.ts +271 -50
  87. package/src/services/feedback-loop.service.ts +96 -0
  88. package/src/services/global-orchestrator.service.ts +148 -0
  89. package/src/services/index.ts +26 -0
  90. package/src/services/institutional-memory.service.ts +145 -0
  91. package/src/services/learned-skill.service.ts +30 -15
  92. package/src/services/memory-assessment.service.ts +3 -2
  93. package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
  94. package/src/services/memory.service.ts +55 -69
  95. package/src/services/monitoring-window.service.ts +86 -0
  96. package/src/services/mutating-approval.service.ts +1 -1
  97. package/src/services/node-workspace.service.ts +155 -0
  98. package/src/services/notification.service.ts +39 -0
  99. package/src/services/organization-member.service.ts +12 -5
  100. package/src/services/organization.service.ts +5 -5
  101. package/src/services/ownership-dispatcher.service.ts +403 -0
  102. package/src/services/plan-approval.service.ts +1 -1
  103. package/src/services/plan-artifact.service.ts +1 -0
  104. package/src/services/plan-builder.service.ts +1 -0
  105. package/src/services/plan-checkpoint.service.ts +30 -2
  106. package/src/services/plan-compiler.service.ts +5 -0
  107. package/src/services/plan-coordination.service.ts +152 -0
  108. package/src/services/plan-cycle.service.ts +284 -0
  109. package/src/services/plan-deadline.service.ts +287 -0
  110. package/src/services/plan-executor.service.ts +386 -58
  111. package/src/services/plan-helpers.ts +15 -0
  112. package/src/services/plan-run.service.ts +41 -7
  113. package/src/services/plan-scheduler.service.ts +240 -0
  114. package/src/services/plan-template.service.ts +117 -0
  115. package/src/services/plan-validator.service.ts +87 -20
  116. package/src/services/plan-workspace.service.ts +83 -0
  117. package/src/services/playbook-registry.service.ts +67 -0
  118. package/src/services/plugin-executor.service.ts +103 -0
  119. package/src/services/quality-metrics.service.ts +132 -0
  120. package/src/services/recent-activity-title.service.ts +3 -10
  121. package/src/services/recent-activity.service.ts +33 -43
  122. package/src/services/skill-resolver.service.ts +19 -0
  123. package/src/services/system-executor.service.ts +105 -0
  124. package/src/services/workstream-message.service.ts +29 -41
  125. package/src/services/workstream-plan-registry.service.ts +22 -0
  126. package/src/services/workstream-title.service.ts +3 -9
  127. package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
  128. package/src/services/workstream-turn.ts +2 -2
  129. package/src/services/workstream.service.ts +55 -65
  130. package/src/services/workstream.types.ts +10 -19
  131. package/src/services/write-intent-validator.service.ts +81 -0
  132. package/src/storage/attachment-parser.ts +1 -1
  133. package/src/storage/attachment-storage.service.ts +4 -4
  134. package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
  135. package/src/storage/generated-document-storage.service.ts +3 -2
  136. package/src/storage/index.ts +2 -2
  137. package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
  138. package/src/system-agents/delegated-agent-factory.ts +5 -2
  139. package/src/system-agents/index.ts +8 -0
  140. package/src/system-agents/memory-reranker.agent.ts +1 -1
  141. package/src/system-agents/memory.agent.ts +1 -1
  142. package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
  143. package/src/tools/execution-plan.tool.ts +17 -19
  144. package/src/tools/fetch-webpage.tool.ts +20 -18
  145. package/src/tools/index.ts +2 -3
  146. package/src/tools/read-file-parts.tool.ts +1 -1
  147. package/src/tools/search-web.tool.ts +18 -15
  148. package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
  149. package/src/tools/team-think.tool.ts +14 -8
  150. package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
  151. package/src/utils/async.ts +3 -2
  152. package/src/utils/date-time.ts +4 -32
  153. package/src/utils/env.ts +8 -0
  154. package/src/utils/errors.ts +47 -0
  155. package/src/utils/hono-error-handler.ts +1 -2
  156. package/src/utils/index.ts +19 -2
  157. package/src/utils/string.ts +128 -1
  158. package/src/workers/bootstrap.ts +2 -2
  159. package/src/workers/index.ts +1 -0
  160. package/src/workers/memory-consolidation.worker.ts +12 -12
  161. package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
  162. package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
  163. package/src/workers/skill-extraction.runner.ts +8 -102
  164. package/src/workers/utils/file-section-chunker.ts +6 -3
  165. package/src/workers/utils/repomix-file-sections.ts +2 -2
  166. package/src/workers/utils/sandbox-error.ts +11 -2
  167. package/src/workers/utils/workstream-message-query.ts +97 -0
  168. package/src/workers/worker-utils.ts +6 -2
  169. package/src/runtime/retrieval-pipeline.ts +0 -3
  170. package/src/runtime.ts +0 -387
  171. package/src/tools/log-hello-world.tool.ts +0 -17
  172. package/src/utils/error.ts +0 -10
  173. /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
  174. /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import type { ToolSet } from 'ai'
2
2
 
3
3
  import type { RecordIdRef } from '../db/record-id'
4
- import type { ReadableUploadMetadata } from '../services/attachment.service'
4
+ import type { ReadableUploadMetadata } from '../storage/attachment-types'
5
5
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
6
6
 
7
7
  export interface LotaRuntimeBackgroundCursor {
@@ -34,7 +34,7 @@ export interface LotaRuntimeWorkspaceProvider {
34
34
  ): Promise<LotaRuntimeWorkspaceLifecycleState> | LotaRuntimeWorkspaceLifecycleState
35
35
  readProfileProjectionState?(
36
36
  workspace: Record<string, unknown>,
37
- ): Promise<LotaRuntimeWorkspaceProjectionState | void> | LotaRuntimeWorkspaceProjectionState | void
37
+ ): Promise<LotaRuntimeWorkspaceProjectionState | undefined> | LotaRuntimeWorkspaceProjectionState | undefined
38
38
  buildPromptSummary?(workspaceId: RecordIdRef): Promise<string | undefined>
39
39
  listRecentDomainEvents?(workspaceId: RecordIdRef, limit?: number): Promise<Array<Record<string, unknown>>>
40
40
  hasActiveKnowledgeSources?(workspaceId: string): Promise<boolean>
@@ -76,11 +76,87 @@ export interface LotaRuntimeTeamThinkToolsParams {
76
76
  toolProviders?: ToolSet
77
77
  }
78
78
 
79
+ export interface BuildContextParams {
80
+ workstream: unknown
81
+ workstreamRef: RecordIdRef
82
+ orgRef: RecordIdRef
83
+ userRef: RecordIdRef
84
+ userName?: string | null
85
+ workspace: Record<string, unknown>
86
+ onboardingActive: boolean
87
+ messageText: string
88
+ linearInstalled: boolean
89
+ githubInstalled: boolean
90
+ indexedRepoContext: unknown
91
+ promptContext: unknown
92
+ workspaceLifecycleState: unknown
93
+ workspaceProfileState: unknown
94
+ promptSummary: string | undefined
95
+ recentDomainEvents: Array<Record<string, unknown>>
96
+ retrievedKnowledgeSection: string | undefined
97
+ [key: string]: unknown
98
+ }
99
+
100
+ export interface AfterTurnParams {
101
+ workstream: unknown
102
+ workstreamRef: RecordIdRef
103
+ orgRef: RecordIdRef
104
+ userRef: RecordIdRef
105
+ userName?: string | null
106
+ onboardingActive: boolean
107
+ referenceUserMessage: unknown
108
+ assistantMessages: unknown[]
109
+ latestWorkstreamRecord: unknown
110
+ latestPersistedState: unknown
111
+ context: Record<string, unknown> | null
112
+ [key: string]: unknown
113
+ }
114
+
115
+ export interface ResolveAgentParams {
116
+ agentId: string
117
+ mode: string
118
+ workstream: unknown
119
+ workstreamRef: RecordIdRef
120
+ orgRef: RecordIdRef
121
+ userRef: RecordIdRef
122
+ userName?: string | null
123
+ onboardingActive: boolean
124
+ linearInstalled: boolean
125
+ githubInstalled: boolean
126
+ reasoningProfile: string
127
+ skills?: string[]
128
+ additionalInstructionSections?: string[]
129
+ context: Record<string, unknown> | null
130
+ [key: string]: unknown
131
+ }
132
+
133
+ export interface BuildExtraInstructionSectionsParams {
134
+ workstream: unknown
135
+ workstreamRef: RecordIdRef
136
+ orgRef: RecordIdRef
137
+ userRef: RecordIdRef
138
+ userName?: string | null
139
+ workspace: Record<string, unknown>
140
+ onboardingActive: boolean
141
+ messageText: string
142
+ linearInstalled: boolean
143
+ githubInstalled: boolean
144
+ indexedRepoContext: unknown
145
+ promptContext: unknown
146
+ workspaceLifecycleState: unknown
147
+ workspaceProfileState: unknown
148
+ promptSummary: string | undefined
149
+ recentDomainEvents: Array<Record<string, unknown>>
150
+ retrievedKnowledgeSection: string | undefined
151
+ context: Record<string, unknown> | null
152
+ [key: string]: unknown
153
+ }
154
+
79
155
  export interface LotaRuntimeTurnHooks {
80
- buildContext?: (params: Record<string, unknown>) => Promise<Record<string, unknown> | void>
81
- afterTurn?: (params: Record<string, unknown>) => Promise<void>
82
- resolveAgent?: (params: Record<string, unknown>) => Promise<Record<string, unknown> | void>
83
- buildExtraInstructionSections?: (params: Record<string, unknown>) => Promise<string[] | void>
156
+ buildContext?: (params: BuildContextParams) => Promise<Record<string, unknown> | void>
157
+ afterTurn?: (params: AfterTurnParams) => Promise<void>
158
+ resolveAgent?: (params: ResolveAgentParams) => Promise<Record<string, unknown> | void>
159
+ buildExtraInstructionSections?: (params: BuildExtraInstructionSectionsParams) => Promise<string[] | void>
84
160
  }
85
161
 
86
162
  export interface LotaRuntimeAdapters {
@@ -90,8 +166,8 @@ export interface LotaRuntimeAdapters {
90
166
  buildTeamThinkAgentTools?: (params: LotaRuntimeTeamThinkToolsParams) => Promise<{ tools: ToolSet }>
91
167
  }
92
168
  queues?: {
93
- enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void> | void
94
- enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void> | void
169
+ enqueuePostChatOrgAction?: (job: Record<string, unknown>) => Promise<void>
170
+ enqueueOnboardingRepoIndexFollowUp?: (job: Record<string, unknown>) => Promise<void>
95
171
  }
96
172
  workers?: {
97
173
  connectPluginDatabases?: () => Promise<void>
@@ -115,17 +191,17 @@ let runtimeExtensionsState: RuntimeExtensionsState = {
115
191
  extraWorkers: {},
116
192
  }
117
193
 
118
- export function configureRuntimeExtensions(params?: {
194
+ export function configureRuntimeExtensions(params: {
119
195
  adapters?: LotaRuntimeAdapters
120
196
  turnHooks?: LotaRuntimeTurnHooks
121
197
  toolProviders?: ToolSet
122
198
  extraWorkers?: LotaRuntimeWorkerExtensions
123
199
  }): void {
124
200
  runtimeExtensionsState = {
125
- adapters: params?.adapters ?? {},
126
- turnHooks: params?.turnHooks ?? {},
127
- toolProviders: params?.toolProviders ?? EMPTY_TOOLS,
128
- extraWorkers: params?.extraWorkers ?? {},
201
+ adapters: params.adapters ?? {},
202
+ turnHooks: params.turnHooks ?? {},
203
+ toolProviders: params.toolProviders ?? EMPTY_TOOLS,
204
+ extraWorkers: params.extraWorkers ?? {},
129
205
  }
130
206
  }
131
207
 
@@ -148,8 +224,8 @@ export function getConfiguredPluginDatabaseConnector(): (() => Promise<void>) |
148
224
  export async function withConfiguredWorkspaceMemoryLock<T>(workspaceId: string, fn: () => Promise<T>): Promise<T> {
149
225
  const adapter = runtimeExtensionsState.adapters.workers?.withWorkspaceMemoryLock
150
226
  if (!adapter) {
151
- return await fn()
227
+ return fn()
152
228
  }
153
229
 
154
- return await adapter(workspaceId, fn)
230
+ return adapter(workspaceId, fn)
155
231
  }
@@ -1,5 +1,7 @@
1
1
  import { startContextCompactionWorker } from '../queues/context-compaction.queue'
2
+ import { startDelayedNodePromotionWorker } from '../queues/delayed-node-promotion.queue'
2
3
  import { scheduleRecurringConsolidation, startMemoryConsolidationWorker } from '../queues/memory-consolidation.queue'
4
+ import { startPlanSchedulerWorker } from '../queues/plan-scheduler.queue'
3
5
  import { startPostChatMemoryWorker } from '../queues/post-chat-memory.queue'
4
6
  import { startRecentActivityTitleRefinementWorker } from '../queues/recent-activity-title-refinement.queue'
5
7
  import { startRegularChatMemoryDigestWorker } from '../queues/regular-chat-memory-digest.queue'
@@ -8,7 +10,9 @@ import { startWorkstreamTitleGenerationWorker } from '../queues/workstream-title
8
10
 
9
11
  export interface LotaRuntimeWorkerStartRegistry {
10
12
  contextCompaction: typeof startContextCompactionWorker
13
+ delayedNodePromotion: typeof startDelayedNodePromotionWorker
11
14
  memoryConsolidation: typeof startMemoryConsolidationWorker
15
+ planScheduler: typeof startPlanSchedulerWorker
12
16
  postChatMemory: typeof startPostChatMemoryWorker
13
17
  regularChatMemoryDigest: typeof startRegularChatMemoryDigestWorker
14
18
  skillExtraction: typeof startSkillExtractionWorker
@@ -34,7 +38,9 @@ export function buildRuntimeWorkerRegistry(extraWorkers?: LotaRuntimeWorkerExten
34
38
  return {
35
39
  start: {
36
40
  contextCompaction: startContextCompactionWorker,
41
+ delayedNodePromotion: startDelayedNodePromotionWorker,
37
42
  memoryConsolidation: startMemoryConsolidationWorker,
43
+ planScheduler: startPlanSchedulerWorker,
38
44
  postChatMemory: startPostChatMemoryWorker,
39
45
  regularChatMemoryDigest: startRegularChatMemoryDigestWorker,
40
46
  skillExtraction: startSkillExtractionWorker,
@@ -1,6 +1,6 @@
1
1
  import { ConsultTeamArgsSchema, withMessageCreatedAt } from '@lota-sdk/shared'
2
2
  import type { ChatMessage, ConsultTeamResultData } from '@lota-sdk/shared'
3
- import { convertToModelMessages, readUIMessageStream, tool as createTool } from 'ai'
3
+ import { convertToModelMessages, tool as createTool } from 'ai'
4
4
 
5
5
  import { agentDisplayNames, teamConsultParticipants } from '../config/agent-defaults'
6
6
  import { createTimedAbortSignal } from './agent-stream-helpers'
@@ -33,6 +33,28 @@ function getConsultTeamOutput(output: unknown): ConsultTeamResultData | undefine
33
33
  return undefined
34
34
  }
35
35
 
36
+ function findLastUserMessage(
37
+ messages: ChatMessage[],
38
+ predicate: (message: ChatMessage) => boolean,
39
+ ): ChatMessage | null {
40
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
41
+ const message = messages[index]
42
+ if (predicate(message)) {
43
+ return message
44
+ }
45
+ }
46
+
47
+ return null
48
+ }
49
+
50
+ function selectTeamConsultationContextMessages(messages: ChatMessage[], latestUserMessageId: string): ChatMessage[] {
51
+ const latestUserMessage =
52
+ findLastUserMessage(messages, (message) => message.id === latestUserMessageId && message.role === 'user') ??
53
+ findLastUserMessage(messages, (message) => message.role === 'user')
54
+
55
+ return latestUserMessage ? [latestUserMessage] : []
56
+ }
57
+
36
58
  interface ParticipantObserver {
37
59
  run<T>(fn: () => T | Promise<T>): Promise<T>
38
60
  recordError?: (error: unknown) => void
@@ -51,6 +73,7 @@ export interface TeamConsultationParticipantRunner {
51
73
  },
52
74
  ): Promise<{
53
75
  agent: {
76
+ generate(params: Record<string, unknown>): Promise<{ text: string }>
54
77
  stream(
55
78
  params: Record<string, unknown>,
56
79
  ): Promise<{
@@ -119,16 +142,22 @@ export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
119
142
  })
120
143
  const modelMessages = await convertToModelMessages(
121
144
  buildModelInputMessagesWithUploadMetadata({
122
- messages: params.historyMessages,
145
+ messages: selectTeamConsultationContextMessages(params.historyMessages, params.latestUserMessageId),
123
146
  latestUserMessageId: params.latestUserMessageId,
124
147
  uploadMetadataText,
125
148
  }),
126
149
  { ignoreIncompleteToolCalls: true },
127
150
  )
128
151
 
129
- let result: Awaited<ReturnType<typeof agent.stream>>
152
+ let result: Awaited<ReturnType<typeof agent.generate>>
130
153
  try {
131
- result = await observer.run(() => agent.stream({ messages: modelMessages, abortSignal: timedAbort.signal }))
154
+ result = await observer.run(() =>
155
+ agent.generate({
156
+ messages: modelMessages,
157
+ abortSignal: timedAbort.signal,
158
+ timeout: TEAM_CONSULTATION_TIMEOUT_MS,
159
+ }),
160
+ )
132
161
  } catch (error) {
133
162
  if (params.abortSignal.aborted || timedAbort.signal.aborted) {
134
163
  observer.recordAbort?.(error)
@@ -138,33 +167,21 @@ export function createConsultTeamTool(params: CreateConsultTeamToolParams) {
138
167
  throw error
139
168
  }
140
169
 
141
- for await (const message of readUIMessageStream<ChatMessage>({
142
- stream: result.toUIMessageStream({
143
- generateMessageId: () => Bun.randomUUIDv7(),
144
- sendReasoning: true,
145
- sendSources: true,
146
- sendStart: false,
147
- sendFinish: false,
148
- }) as ReadableStream<never>,
149
- onError: (error) => {
150
- params.onReadError?.(agentId, error)
151
- },
152
- })) {
153
- latestMessage = withMessageCreatedAt(message)
154
- responses[index] = {
155
- agentId,
156
- agentName,
157
- status: 'running',
158
- summary: extractMessageText(latestMessage).trim() || undefined,
159
- message: latestMessage,
160
- }
161
- pushSnapshot()
162
- }
163
-
164
- if (!latestMessage) {
170
+ const responseText = result.text.trim()
171
+ if (!responseText) {
165
172
  throw new Error(`Team participant ${agentId} did not produce a response.`)
166
173
  }
167
174
 
175
+ latestMessage = withMessageCreatedAt(
176
+ {
177
+ id: Bun.randomUUIDv7(),
178
+ role: 'assistant',
179
+ parts: [{ type: 'text', text: responseText }],
180
+ metadata: { agentId, agentName },
181
+ } satisfies ChatMessage,
182
+ Date.now(),
183
+ )
184
+
168
185
  responses[index] = {
169
186
  agentId,
170
187
  agentName,
@@ -3,12 +3,21 @@ import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defa
3
3
  export function buildTeamConsultationResponseGuard(params: { agentId: string; task: string }) {
4
4
  const agentName = agentDisplayNames[params.agentId] ?? params.agentId
5
5
  const leadAgentDisplayName = getLeadAgentDisplayName()
6
+ const mentorConstraint =
7
+ params.agentId === 'mentor'
8
+ ? ['- As Mentor, answer as an experienced operator reviewing launch discipline, not as a coach or therapist.']
9
+ : []
6
10
  return [
7
11
  '<team-consultation-agent-protocol>',
8
12
  `- You are participating in a structured internal team consultation led by ${leadAgentDisplayName}.`,
9
13
  `- Your role for this response is ${agentName}.`,
10
- '- Use markdown when it helps clarity.',
11
- '- Make the recommendation, explain key tradeoffs, and note the next decision.',
14
+ '- Chief is already coordinating this panel. Do not refer the task back to Chief or tell the user to consult another agent.',
15
+ '- Answer only from your role-specific perspective for the task below.',
16
+ '- Do not coach the user, ask follow-up questions, or challenge assumptions in this panel response. Give the recommendation directly.',
17
+ ...mentorConstraint,
18
+ '- Return exactly 3 short markdown bullets in this order: recommendation, key risk/tradeoff, next decision.',
19
+ '- Keep the whole response under 120 words.',
20
+ '- Do not use headings, code fences, XML, evidence blocks, gap lists, or preamble text.',
12
21
  '',
13
22
  '<team-consultation-task>',
14
23
  params.task.trim(),
@@ -1,12 +1,19 @@
1
+ import { compactWhitespace, truncateText } from '../utils/string'
2
+
1
3
  const TITLE_WORD_LIMIT = 5
2
4
 
3
5
  export function limitTitleWords(text: string): string {
4
- const words = text.replace(/\s+/g, ' ').trim().split(' ').filter(Boolean)
6
+ const words = compactWhitespace(text).split(' ').filter(Boolean)
5
7
  return words.slice(0, TITLE_WORD_LIMIT).join(' ')
6
8
  }
7
9
 
8
10
  export function deriveTitle(text: string): string {
9
- const trimmed = text.replace(/\s+/g, ' ').trim()
10
- if (trimmed.length <= 60) return trimmed
11
- return `${trimmed.slice(0, 57)}...`
11
+ return truncateText(compactWhitespace(text), 60)
12
+ }
13
+
14
+ export function normalizeTitle(value: string): string {
15
+ const normalized = compactWhitespace(value)
16
+ .replace(/^["'`]+|["'`]+$/g, '')
17
+ .replace(/[.!?,;:]+$/g, '')
18
+ return normalized.length <= 80 ? normalized : normalized.slice(0, 80).trim()
12
19
  }
@@ -77,7 +77,7 @@ export function appendPersistedWorkstreamContextToHistoryMessages(
77
77
  nextHistoryMessages.push({ role: 'agent', content: `Compacted chat summary:\n${compactionSummary}` })
78
78
  }
79
79
 
80
- if (params.persistedState !== undefined && params.persistedState !== null) {
80
+ if (params.persistedState !== null && params.persistedState !== undefined) {
81
81
  nextHistoryMessages.push({
82
82
  role: 'agent',
83
83
  content: `Structured workstream state:\n${JSON.stringify(params.persistedState)}`,
@@ -114,14 +114,13 @@ export function collectToolOutputErrors(params: {
114
114
  if (typeof part !== 'object') continue
115
115
  if (part.type !== undefined && typeof part.type !== 'string') continue
116
116
  if (!part.type?.startsWith('tool-')) continue
117
- if ((part as Record<string, unknown>).state !== 'output-error') continue
117
+
118
+ const p = part as Record<string, unknown>
119
+ if (p.state !== 'output-error') continue
118
120
 
119
121
  const toolName = part.type.slice('tool-'.length) || 'unknown'
120
- const toolCallId =
121
- typeof (part as Record<string, unknown>).toolCallId === 'string' && (part as Record<string, unknown>).toolCallId
122
- ? ((part as Record<string, unknown>).toolCallId as string)
123
- : 'unknown'
124
- const errorTextRaw = (part as Record<string, unknown>).errorText
122
+ const toolCallId = typeof p.toolCallId === 'string' && p.toolCallId ? p.toolCallId : 'unknown'
123
+ const errorTextRaw = p.errorText
125
124
  const errorText =
126
125
  typeof errorTextRaw === 'string' && errorTextRaw.trim()
127
126
  ? errorTextRaw.trim()
@@ -36,36 +36,6 @@ export function uniqueMentionOrder(message: string): string[] {
36
36
  return ordered
37
37
  }
38
38
 
39
- const GTM_STRONG_INTENT_PATTERNS: RegExp[] = [
40
- /\bgo[-\s]?to[-\s]?market\b/i,
41
- /\bgtm\b/i,
42
- /\bcontent\s+marketing\b/i,
43
- /\bcontent\s+strategy\b/i,
44
- /\bdemand\s+generation\b/i,
45
- /\bdistribution\s+strategy\b/i,
46
- /\bpositioning\s+strategy\b/i,
47
- /\blaunch\s+strategy\b/i,
48
- /\blaunch\s+plan\b/i,
49
- ]
50
-
51
- const GTM_WEAK_INTENT_PATTERNS: RegExp[] = [/\blaunch\b/i, /\bcommunity\b/i, /\bdistribution\b/i, /\bpositioning\b/i]
52
-
53
- export function isGtmIntentMessage(message: string): boolean {
54
- const text = message.trim()
55
- if (!text) return false
56
-
57
- if (GTM_STRONG_INTENT_PATTERNS.some((pattern) => pattern.test(text))) {
58
- return true
59
- }
60
-
61
- let weakMatches = 0
62
- for (const pattern of GTM_WEAK_INTENT_PATTERNS) {
63
- if (pattern.test(text)) weakMatches += 1
64
- if (weakMatches >= 2) return true
65
- }
66
- return false
67
- }
68
-
69
39
  const HIGH_IMPACT_CLASS_PATTERNS: Array<{ className: HighImpactResponseClass; patterns: RegExp[] }> = [
70
40
  {
71
41
  className: 'architecture-recommendation',
@@ -238,13 +238,23 @@ export interface CompactionOutput {
238
238
  stateDelta: WorkstreamStateDelta
239
239
  }
240
240
 
241
- export const WORKSTREAM_STATE_MAX_KEY_DECISIONS = 8
242
- export const WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS = 6
243
- export const WORKSTREAM_STATE_MAX_TASKS = 10
244
- export const WORKSTREAM_STATE_MAX_OPEN_QUESTIONS = 5
245
- export const WORKSTREAM_STATE_MAX_RISKS = 5
246
- export const WORKSTREAM_STATE_MAX_ARTIFACTS = 10
247
- export const WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS = 6
241
+ const WORKSTREAM_STATE_MAX_KEY_DECISIONS = 8
242
+ const WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS = 6
243
+ const WORKSTREAM_STATE_MAX_TASKS = 10
244
+ const WORKSTREAM_STATE_MAX_OPEN_QUESTIONS = 5
245
+ const WORKSTREAM_STATE_MAX_RISKS = 5
246
+ const WORKSTREAM_STATE_MAX_ARTIFACTS = 10
247
+ const WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS = 6
248
+
249
+ export function applyWorkstreamStateCaps(state: WorkstreamState): void {
250
+ state.keyDecisions = state.keyDecisions.slice(-WORKSTREAM_STATE_MAX_KEY_DECISIONS)
251
+ state.activeConstraints = state.activeConstraints.slice(-WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS)
252
+ state.tasks = state.tasks.slice(-WORKSTREAM_STATE_MAX_TASKS)
253
+ state.openQuestions = state.openQuestions.slice(-WORKSTREAM_STATE_MAX_OPEN_QUESTIONS)
254
+ state.risks = state.risks.slice(-WORKSTREAM_STATE_MAX_RISKS)
255
+ state.artifacts = state.artifacts.slice(-WORKSTREAM_STATE_MAX_ARTIFACTS)
256
+ state.agentContributions = state.agentContributions.slice(-WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS)
257
+ }
248
258
 
249
259
  export function createEmptyWorkstreamState(now = Date.now()): WorkstreamState {
250
260
  return {
@@ -0,0 +1,152 @@
1
+ import type { PlaybookVersion, Recommendation } from '@lota-sdk/shared'
2
+ import { PlaybookSchema, PlaybookVersionSchema } from '@lota-sdk/shared'
3
+
4
+ import { ensureRecordId, recordIdToString } from '../db/record-id'
5
+ import { databaseService } from '../db/service'
6
+ import { TABLES } from '../db/tables'
7
+
8
+ class AdaptivePlaybookService {
9
+ async refineFromCycle(params: {
10
+ playbookId: string
11
+ runId: string
12
+ recommendations: Recommendation[]
13
+ organizationId: string
14
+ }): Promise<PlaybookVersion> {
15
+ const playbook = await databaseService.findOne(
16
+ TABLES.PLAYBOOK,
17
+ { id: ensureRecordId(params.playbookId, TABLES.PLAYBOOK) },
18
+ PlaybookSchema,
19
+ )
20
+ if (!playbook) {
21
+ throw new Error(`Playbook not found: ${params.playbookId}`)
22
+ }
23
+
24
+ const currentVersion = await databaseService.findOne(
25
+ TABLES.PLAYBOOK_VERSION,
26
+ {
27
+ id: ensureRecordId(
28
+ recordIdToString(playbook.currentVersionId, TABLES.PLAYBOOK_VERSION),
29
+ TABLES.PLAYBOOK_VERSION,
30
+ ),
31
+ },
32
+ PlaybookVersionSchema,
33
+ )
34
+
35
+ const nextVersionNumber = currentVersion ? currentVersion.version + 1 : 1
36
+ const now = new Date()
37
+
38
+ const newVersion = await databaseService.create(
39
+ TABLES.PLAYBOOK_VERSION,
40
+ {
41
+ playbookId: ensureRecordId(params.playbookId, TABLES.PLAYBOOK),
42
+ version: nextVersionNumber,
43
+ parentVersionId: playbook.currentVersionId,
44
+ appliedRecommendations: params.recommendations.map((r) => r.description),
45
+ status: 'testing',
46
+ createdAt: now,
47
+ },
48
+ PlaybookVersionSchema,
49
+ )
50
+
51
+ await databaseService.update(
52
+ TABLES.PLAYBOOK,
53
+ ensureRecordId(params.playbookId, TABLES.PLAYBOOK),
54
+ {
55
+ currentVersionId: newVersion.id,
56
+ previousVersionId: playbook.currentVersionId,
57
+ cycleCount: playbook.cycleCount + 1,
58
+ },
59
+ PlaybookSchema,
60
+ )
61
+
62
+ return newVersion
63
+ }
64
+
65
+ evaluateRegression(params: { currentScore: number; previousScore: number; threshold?: number }): {
66
+ shouldRollback: boolean
67
+ } {
68
+ const threshold = params.threshold ?? 0.9
69
+ if (params.previousScore === 0) return { shouldRollback: false }
70
+ return { shouldRollback: params.currentScore < params.previousScore * threshold }
71
+ }
72
+
73
+ async rollback(params: {
74
+ playbookId: string
75
+ organizationId: string
76
+ maxLevels?: number
77
+ }): Promise<PlaybookVersion | null> {
78
+ const maxLevels = params.maxLevels ?? 3
79
+ return this.rollbackRecursive(params.playbookId, params.organizationId, maxLevels)
80
+ }
81
+
82
+ private async rollbackRecursive(
83
+ playbookId: string,
84
+ organizationId: string,
85
+ remainingLevels: number,
86
+ ): Promise<PlaybookVersion | null> {
87
+ if (remainingLevels <= 0) return null
88
+
89
+ const playbook = await databaseService.findOne(
90
+ TABLES.PLAYBOOK,
91
+ { id: ensureRecordId(playbookId, TABLES.PLAYBOOK) },
92
+ PlaybookSchema,
93
+ )
94
+ if (!playbook) return null
95
+
96
+ const currentVersion = await databaseService.findOne(
97
+ TABLES.PLAYBOOK_VERSION,
98
+ {
99
+ id: ensureRecordId(
100
+ recordIdToString(playbook.currentVersionId, TABLES.PLAYBOOK_VERSION),
101
+ TABLES.PLAYBOOK_VERSION,
102
+ ),
103
+ },
104
+ PlaybookVersionSchema,
105
+ )
106
+ if (!currentVersion?.parentVersionId) return null
107
+
108
+ // Mark current version as rolled-back
109
+ await databaseService.update(
110
+ TABLES.PLAYBOOK_VERSION,
111
+ ensureRecordId(recordIdToString(currentVersion.id, TABLES.PLAYBOOK_VERSION), TABLES.PLAYBOOK_VERSION),
112
+ { status: 'rolled-back' },
113
+ PlaybookVersionSchema,
114
+ )
115
+
116
+ // Restore parent version as active
117
+ const parentVersion = await databaseService.findOne(
118
+ TABLES.PLAYBOOK_VERSION,
119
+ {
120
+ id: ensureRecordId(
121
+ recordIdToString(currentVersion.parentVersionId, TABLES.PLAYBOOK_VERSION),
122
+ TABLES.PLAYBOOK_VERSION,
123
+ ),
124
+ },
125
+ PlaybookVersionSchema,
126
+ )
127
+ if (!parentVersion) return null
128
+
129
+ await databaseService.update(
130
+ TABLES.PLAYBOOK_VERSION,
131
+ ensureRecordId(recordIdToString(parentVersion.id, TABLES.PLAYBOOK_VERSION), TABLES.PLAYBOOK_VERSION),
132
+ { status: 'active' },
133
+ PlaybookVersionSchema,
134
+ )
135
+
136
+ await databaseService.update(
137
+ TABLES.PLAYBOOK,
138
+ ensureRecordId(playbookId, TABLES.PLAYBOOK),
139
+ { currentVersionId: parentVersion.id, previousVersionId: parentVersion.parentVersionId },
140
+ PlaybookSchema,
141
+ )
142
+
143
+ // Check if parent version is also regressed (caller can evaluate and re-invoke)
144
+ if (parentVersion.qualityScore !== undefined && parentVersion.qualityScore < 0.5) {
145
+ return this.rollbackRecursive(playbookId, organizationId, remainingLevels - 1)
146
+ }
147
+
148
+ return parentVersion
149
+ }
150
+ }
151
+
152
+ export const adaptivePlaybookService = new AdaptivePlaybookService()