@lota-sdk/core 0.1.5

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 (153) hide show
  1. package/infrastructure/schema/00_workstream.surql +55 -0
  2. package/infrastructure/schema/01_memory.surql +47 -0
  3. package/infrastructure/schema/02_execution_plan.surql +62 -0
  4. package/infrastructure/schema/03_learned_skill.surql +32 -0
  5. package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
  6. package/package.json +128 -0
  7. package/src/ai/definitions.ts +308 -0
  8. package/src/bifrost/bifrost.ts +256 -0
  9. package/src/config/agent-defaults.ts +99 -0
  10. package/src/config/constants.ts +33 -0
  11. package/src/config/env-shapes.ts +122 -0
  12. package/src/config/logger.ts +29 -0
  13. package/src/config/model-constants.ts +31 -0
  14. package/src/config/search.ts +17 -0
  15. package/src/config/workstream-defaults.ts +68 -0
  16. package/src/db/base.service.ts +55 -0
  17. package/src/db/cursor-pagination.ts +73 -0
  18. package/src/db/memory-query-builder.ts +207 -0
  19. package/src/db/memory-store.helpers.ts +118 -0
  20. package/src/db/memory-store.rows.ts +29 -0
  21. package/src/db/memory-store.ts +974 -0
  22. package/src/db/memory-types.ts +193 -0
  23. package/src/db/memory.ts +505 -0
  24. package/src/db/record-id.ts +78 -0
  25. package/src/db/service.ts +932 -0
  26. package/src/db/startup.ts +152 -0
  27. package/src/db/tables.ts +20 -0
  28. package/src/document/org-document-chunking.ts +224 -0
  29. package/src/document/parsing.ts +40 -0
  30. package/src/embeddings/provider.ts +76 -0
  31. package/src/index.ts +302 -0
  32. package/src/queues/context-compaction.queue.ts +82 -0
  33. package/src/queues/document-processor.queue.ts +118 -0
  34. package/src/queues/memory-consolidation.queue.ts +65 -0
  35. package/src/queues/post-chat-memory.queue.ts +128 -0
  36. package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
  37. package/src/queues/regular-chat-memory-digest.config.ts +12 -0
  38. package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
  39. package/src/queues/skill-extraction.config.ts +9 -0
  40. package/src/queues/skill-extraction.queue.ts +62 -0
  41. package/src/redis/connection.ts +176 -0
  42. package/src/redis/index.ts +30 -0
  43. package/src/redis/org-memory-lock.ts +43 -0
  44. package/src/redis/redis-lease-lock.ts +158 -0
  45. package/src/runtime/agent-contract.ts +1 -0
  46. package/src/runtime/agent-prompt-context.ts +119 -0
  47. package/src/runtime/agent-runtime-policy.ts +192 -0
  48. package/src/runtime/agent-stream-helpers.ts +117 -0
  49. package/src/runtime/agent-types.ts +22 -0
  50. package/src/runtime/approval-continuation.ts +16 -0
  51. package/src/runtime/chat-attachments.ts +46 -0
  52. package/src/runtime/chat-message.ts +10 -0
  53. package/src/runtime/chat-request-routing.ts +21 -0
  54. package/src/runtime/chat-run-orchestration.ts +25 -0
  55. package/src/runtime/chat-run-registry.ts +20 -0
  56. package/src/runtime/chat-types.ts +18 -0
  57. package/src/runtime/context-compaction-constants.ts +11 -0
  58. package/src/runtime/context-compaction-runtime.ts +86 -0
  59. package/src/runtime/context-compaction.ts +909 -0
  60. package/src/runtime/execution-plan.ts +59 -0
  61. package/src/runtime/helper-model.ts +405 -0
  62. package/src/runtime/indexed-repositories-policy.ts +28 -0
  63. package/src/runtime/instruction-sections.ts +8 -0
  64. package/src/runtime/llm-content.ts +71 -0
  65. package/src/runtime/memory-block.ts +264 -0
  66. package/src/runtime/memory-digest-policy.ts +14 -0
  67. package/src/runtime/memory-format.ts +8 -0
  68. package/src/runtime/memory-pipeline.ts +570 -0
  69. package/src/runtime/memory-prompts-fact.ts +47 -0
  70. package/src/runtime/memory-prompts-parse.ts +3 -0
  71. package/src/runtime/memory-prompts-update.ts +37 -0
  72. package/src/runtime/memory-scope.ts +43 -0
  73. package/src/runtime/plugin-types.ts +10 -0
  74. package/src/runtime/retrieval-adapters.ts +25 -0
  75. package/src/runtime/retrieval-pipeline.ts +3 -0
  76. package/src/runtime/runtime-extensions.ts +154 -0
  77. package/src/runtime/skill-extraction-policy.ts +3 -0
  78. package/src/runtime/team-consultation-orchestrator.ts +245 -0
  79. package/src/runtime/team-consultation-prompts.ts +32 -0
  80. package/src/runtime/title-helpers.ts +12 -0
  81. package/src/runtime/turn-lifecycle.ts +28 -0
  82. package/src/runtime/workstream-chat-helpers.ts +187 -0
  83. package/src/runtime/workstream-routing-policy.ts +301 -0
  84. package/src/runtime/workstream-state.ts +261 -0
  85. package/src/services/attachment.service.ts +159 -0
  86. package/src/services/chat-attachments.service.ts +17 -0
  87. package/src/services/chat-run-registry.service.ts +3 -0
  88. package/src/services/context-compaction-runtime.ts +13 -0
  89. package/src/services/context-compaction.service.ts +115 -0
  90. package/src/services/document-chunk.service.ts +141 -0
  91. package/src/services/execution-plan.service.ts +890 -0
  92. package/src/services/learned-skill.service.ts +328 -0
  93. package/src/services/memory-assessment.service.ts +43 -0
  94. package/src/services/memory.service.ts +807 -0
  95. package/src/services/memory.utils.ts +84 -0
  96. package/src/services/mutating-approval.service.ts +110 -0
  97. package/src/services/recent-activity-title.service.ts +74 -0
  98. package/src/services/recent-activity.service.ts +397 -0
  99. package/src/services/workstream-change-tracker.service.ts +313 -0
  100. package/src/services/workstream-message.service.ts +283 -0
  101. package/src/services/workstream-title.service.ts +58 -0
  102. package/src/services/workstream-turn-preparation.ts +1340 -0
  103. package/src/services/workstream-turn.ts +37 -0
  104. package/src/services/workstream.service.ts +854 -0
  105. package/src/services/workstream.types.ts +118 -0
  106. package/src/storage/attachment-parser.ts +101 -0
  107. package/src/storage/attachment-storage.service.ts +391 -0
  108. package/src/storage/attachments.types.ts +11 -0
  109. package/src/storage/attachments.utils.ts +58 -0
  110. package/src/storage/generated-document-storage.service.ts +55 -0
  111. package/src/system-agents/agent-result.ts +27 -0
  112. package/src/system-agents/context-compacter.agent.ts +46 -0
  113. package/src/system-agents/delegated-agent-factory.ts +177 -0
  114. package/src/system-agents/helper-agent-options.ts +20 -0
  115. package/src/system-agents/memory-reranker.agent.ts +38 -0
  116. package/src/system-agents/memory.agent.ts +58 -0
  117. package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
  118. package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
  119. package/src/system-agents/researcher.agent.ts +34 -0
  120. package/src/system-agents/skill-extractor.agent.ts +88 -0
  121. package/src/system-agents/skill-manager.agent.ts +80 -0
  122. package/src/system-agents/title-generator.agent.ts +42 -0
  123. package/src/system-agents/workstream-tracker.agent.ts +58 -0
  124. package/src/tools/execution-plan.tool.ts +163 -0
  125. package/src/tools/fetch-webpage.tool.ts +132 -0
  126. package/src/tools/firecrawl-client.ts +12 -0
  127. package/src/tools/memory-block.tool.ts +55 -0
  128. package/src/tools/read-file-parts.tool.ts +80 -0
  129. package/src/tools/remember-memory.tool.ts +85 -0
  130. package/src/tools/research-topic.tool.ts +15 -0
  131. package/src/tools/search-tools.ts +55 -0
  132. package/src/tools/search-web.tool.ts +175 -0
  133. package/src/tools/team-think.tool.ts +125 -0
  134. package/src/tools/tool-contract.ts +21 -0
  135. package/src/tools/user-questions.tool.ts +18 -0
  136. package/src/utils/async.ts +50 -0
  137. package/src/utils/date-time.ts +34 -0
  138. package/src/utils/error.ts +10 -0
  139. package/src/utils/errors.ts +28 -0
  140. package/src/utils/hono-error-handler.ts +71 -0
  141. package/src/utils/string.ts +51 -0
  142. package/src/workers/bootstrap.ts +44 -0
  143. package/src/workers/memory-consolidation.worker.ts +318 -0
  144. package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
  145. package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
  146. package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
  147. package/src/workers/skill-extraction.runner.ts +331 -0
  148. package/src/workers/skill-extraction.worker.ts +22 -0
  149. package/src/workers/utils/repo-indexer-chunker.ts +331 -0
  150. package/src/workers/utils/repo-structure-extractor.ts +645 -0
  151. package/src/workers/utils/repomix-process-concurrency.ts +65 -0
  152. package/src/workers/utils/sandbox-error.ts +5 -0
  153. package/src/workers/worker-utils.ts +182 -0
@@ -0,0 +1,59 @@
1
+ import type { SerializableExecutionPlan } from '@lota-sdk/shared/schemas/execution-plan'
2
+
3
+ export const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
4
+ - Before doing multi-step work, create an execution plan instead of tracking steps only in prose.
5
+ - Keep plans short and operational. Prefer 2 to 7 tasks unless the work truly needs more.
6
+ - Use execution-plan tools to create, replace, update, inspect, and restart the plan.
7
+ - Only one execution-plan task may be active at a time.
8
+ - Every task should have a concrete rationale and a concise output summary when work happens.
9
+ - Treat prior task output summaries and carried tasks in <execution-plan-state> as observed facts.
10
+ - If the ordered steps materially change, replace the plan instead of silently rewriting it in prose.
11
+ - If the active plan is blocked, do not continue blindly. Replan, restart a task, ask for user input, or abort.
12
+ </execution-plan-protocol>`
13
+
14
+ export function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | undefined): string | undefined {
15
+ if (!plan) return undefined
16
+
17
+ const payload = {
18
+ policy: {
19
+ activePlanIsAuthoritative: true,
20
+ priorOutputSummariesAreObservedFacts: true,
21
+ singleActiveTask: true,
22
+ explicitReplanRequired: true,
23
+ failureBudget: 2,
24
+ },
25
+ plan,
26
+ }
27
+
28
+ return ['<execution-plan-state>', JSON.stringify(payload, null, 2), '</execution-plan-state>'].join('\n')
29
+ }
30
+
31
+ export function buildExecutionPlanInstructionSections(
32
+ plan: SerializableExecutionPlan | null | undefined,
33
+ ): string[] | undefined {
34
+ const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
35
+ const executionPlanStateSection = formatExecutionPlanForPrompt(plan)
36
+ if (executionPlanStateSection) {
37
+ sections.push(executionPlanStateSection)
38
+ }
39
+ return sections
40
+ }
41
+
42
+ export function createExecutionPlanInstructionSectionCache(params: {
43
+ disabled?: boolean
44
+ loadPlan: () => Promise<SerializableExecutionPlan | null | undefined>
45
+ }) {
46
+ let sectionsPromise: Promise<string[] | undefined> | null = null
47
+
48
+ return {
49
+ invalidate() {
50
+ sectionsPromise = null
51
+ },
52
+ async getSections(): Promise<string[] | undefined> {
53
+ if (params.disabled) return undefined
54
+
55
+ sectionsPromise ??= params.loadPlan().then((plan) => buildExecutionPlanInstructionSections(plan))
56
+ return await sectionsPromise
57
+ },
58
+ }
59
+ }
@@ -0,0 +1,405 @@
1
+ import { Output } from 'ai'
2
+ import type {
3
+ TimeoutConfiguration,
4
+ ToolLoopAgentOnFinishCallback,
5
+ ToolLoopAgentOnStepFinishCallback,
6
+ ToolSet,
7
+ } from 'ai'
8
+ import type { ZodSchema } from 'zod'
9
+
10
+ export interface HelperToolLoopAgentOptions {
11
+ instructions?: string
12
+ maxOutputTokens?: number
13
+ temperature?: number
14
+ output?: Output.Output
15
+ maxRetries?: number
16
+ }
17
+
18
+ export interface HelperMessage {
19
+ role: 'system' | 'user' | 'assistant'
20
+ content: string
21
+ }
22
+
23
+ interface HelperAgentGenerateParams {
24
+ messages: Array<{ role: 'user' | 'assistant'; content: string }>
25
+ timeout?: TimeoutConfiguration
26
+ onStepFinish?: ToolLoopAgentOnStepFinishCallback<ToolSet>
27
+ onFinish?: ToolLoopAgentOnFinishCallback<ToolSet>
28
+ }
29
+
30
+ export interface HelperAgent {
31
+ generate(params: HelperAgentGenerateParams): Promise<{ text?: unknown; output?: unknown }>
32
+ }
33
+
34
+ export type CreateHelperAgentFn = (options: HelperToolLoopAgentOptions) => HelperAgent
35
+
36
+ export interface GenerateHelperTextParams {
37
+ tag: string
38
+ createAgent: CreateHelperAgentFn
39
+ messages: HelperMessage[]
40
+ systemPrompt?: string
41
+ defaultSystemPrompt?: string
42
+ temperature?: number
43
+ maxOutputTokens?: number
44
+ timeoutMs?: number
45
+ }
46
+
47
+ export interface GenerateHelperStructuredParams<T> extends Omit<GenerateHelperTextParams, 'tag'> {
48
+ tag: string
49
+ schema: ZodSchema<T>
50
+ textFallbackParser?: (text: string) => T | null
51
+ }
52
+
53
+ function isObject(value: unknown): value is Record<string, unknown> {
54
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
55
+ }
56
+
57
+ function getNumericField(value: Record<string, unknown>, key: string): number | null {
58
+ const field = value[key]
59
+ if (typeof field === 'number' && Number.isFinite(field)) return field
60
+ if (typeof field === 'string') {
61
+ const parsed = Number(field)
62
+ if (Number.isFinite(parsed)) return parsed
63
+ }
64
+ return null
65
+ }
66
+
67
+ function getErrorStatus(error: unknown): number | null {
68
+ if (!isObject(error)) return null
69
+ return getNumericField(error, 'status') ?? getNumericField(error, 'statusCode')
70
+ }
71
+
72
+ function isRateLimitError(error: unknown): boolean {
73
+ return getErrorStatus(error) === 429
74
+ }
75
+
76
+ function stringifyUnknown(value: unknown, maxChars: number): string | null {
77
+ if (value === null || value === undefined) return null
78
+ const raw = (() => {
79
+ if (typeof value === 'string') return value
80
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
81
+ return String(value)
82
+ }
83
+ if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
84
+ if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
85
+ if (Array.isArray(value)) {
86
+ try {
87
+ return JSON.stringify(value)
88
+ } catch {
89
+ return `[array(${value.length})]`
90
+ }
91
+ }
92
+
93
+ try {
94
+ return JSON.stringify(value)
95
+ } catch {
96
+ const maybeName =
97
+ isObject(value) && typeof (value as { constructor?: { name?: unknown } }).constructor?.name === 'string'
98
+ ? (value as { constructor?: { name?: string } }).constructor?.name
99
+ : 'Object'
100
+ return `[object ${maybeName}]`
101
+ }
102
+ })()
103
+
104
+ const normalized = raw.replace(/\s+/g, ' ').trim()
105
+ if (!normalized) return null
106
+ return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}...` : normalized
107
+ }
108
+
109
+ function formatError(tag: string, error: unknown): Error {
110
+ const status = getErrorStatus(error)
111
+ const rateLimited = isRateLimitError(error)
112
+ const message = error instanceof Error ? error.message : String(error)
113
+ const errorRecord = isObject(error) ? error : null
114
+ const responseBody = errorRecord ? stringifyUnknown(errorRecord.responseBody, 600) : null
115
+ const responseData = errorRecord ? stringifyUnknown(errorRecord.data, 600) : null
116
+ const requestUrl = errorRecord ? stringifyUnknown(errorRecord.url, 200) : null
117
+
118
+ const parts = [`[${tag}]`]
119
+ if (status !== null) parts.push(`status=${status}`)
120
+ if (rateLimited) parts.push('rate_limited')
121
+ parts.push(message)
122
+ if (responseData) parts.push(`provider_data=${responseData}`)
123
+ if (responseBody) parts.push(`response_body=${responseBody}`)
124
+ if (requestUrl) parts.push(`url=${requestUrl}`)
125
+
126
+ return new Error(parts.join(' '))
127
+ }
128
+
129
+ function toModelMessages(messages: HelperMessage[]): Array<{ role: 'user' | 'assistant'; content: string }> {
130
+ return messages
131
+ .filter((message): message is HelperMessage & { role: 'user' | 'assistant' } => message.role !== 'system')
132
+ .map((message) => ({ role: message.role, content: message.content }))
133
+ }
134
+
135
+ function resolveSystemPrompt(params: {
136
+ explicitSystemPrompt?: string
137
+ defaultSystemPrompt?: string
138
+ }): string | undefined {
139
+ if (typeof params.explicitSystemPrompt === 'string') {
140
+ const explicit = params.explicitSystemPrompt.trim()
141
+ return explicit.length > 0 ? explicit : undefined
142
+ }
143
+
144
+ const fallback = params.defaultSystemPrompt?.trim()
145
+ return fallback && fallback.length > 0 ? fallback : undefined
146
+ }
147
+
148
+ function resolveStructuredSystemPrompt(systemPrompt?: string): string {
149
+ const parts = [
150
+ systemPrompt?.trim(),
151
+ 'Return only a valid JSON object that matches the required schema. Do not wrap it in markdown or code fences.',
152
+ ].filter((part): part is string => Boolean(part && part.length > 0))
153
+
154
+ return parts.join('\n\n')
155
+ }
156
+
157
+ function formatSchemaIssueSummary(issues: Array<{ path: PropertyKey[]; message: string }>): string {
158
+ return issues
159
+ .slice(0, 5)
160
+ .map((issue) => `${issue.path.map((segment) => String(segment)).join('.') || 'root'}: ${issue.message}`)
161
+ .join('; ')
162
+ }
163
+
164
+ export function extractJsonObjectCandidates(text: string): string[] {
165
+ const trimmed = text.trim()
166
+ if (!trimmed) return []
167
+
168
+ const candidates: string[] = [trimmed]
169
+ const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)
170
+ if (fencedMatch?.[1]) {
171
+ candidates.push(fencedMatch[1].trim())
172
+ }
173
+
174
+ let start = -1
175
+ let depth = 0
176
+ let inString = false
177
+ let escaping = false
178
+
179
+ for (let index = 0; index < trimmed.length; index += 1) {
180
+ const character = trimmed[index]
181
+
182
+ if (escaping) {
183
+ escaping = false
184
+ continue
185
+ }
186
+
187
+ if (character === '\\') {
188
+ escaping = true
189
+ continue
190
+ }
191
+
192
+ if (character === '"') {
193
+ inString = !inString
194
+ continue
195
+ }
196
+
197
+ if (inString) continue
198
+
199
+ if (character === '{') {
200
+ if (depth === 0) start = index
201
+ depth += 1
202
+ continue
203
+ }
204
+
205
+ if (character === '}') {
206
+ depth -= 1
207
+ if (depth === 0 && start >= 0) {
208
+ candidates.push(trimmed.slice(start, index + 1))
209
+ start = -1
210
+ }
211
+ }
212
+ }
213
+
214
+ return [...new Set(candidates.filter((candidate) => candidate.length > 0))]
215
+ }
216
+
217
+ function parseStructuredCandidate<T>(params: {
218
+ schema: ZodSchema<T>
219
+ candidate: unknown
220
+ }): { data: T; source: string } | null {
221
+ const direct = params.schema.safeParse(params.candidate)
222
+ if (direct.success) {
223
+ return { data: direct.data, source: 'root' }
224
+ }
225
+
226
+ if (isObject(params.candidate)) {
227
+ for (const key of ['output', 'result', 'data'] as const) {
228
+ const nested = params.candidate[key]
229
+ const nestedParsed = params.schema.safeParse(nested)
230
+ if (nestedParsed.success) {
231
+ return { data: nestedParsed.data, source: key }
232
+ }
233
+ }
234
+ }
235
+
236
+ return null
237
+ }
238
+
239
+ function parseStructuredTextFallback<T>(params: { schema: ZodSchema<T>; text: string }): T {
240
+ const candidates = extractJsonObjectCandidates(params.text)
241
+ if (candidates.length === 0) {
242
+ throw new Error('Structured fallback did not contain a JSON object candidate.')
243
+ }
244
+
245
+ let lastError = 'Structured fallback could not be parsed.'
246
+
247
+ for (const candidateText of candidates) {
248
+ let parsedJson: unknown
249
+
250
+ try {
251
+ parsedJson = JSON.parse(candidateText) as unknown
252
+ } catch {
253
+ lastError = 'Structured fallback JSON parsing failed.'
254
+ continue
255
+ }
256
+
257
+ const parsed = parseStructuredCandidate({ schema: params.schema, candidate: parsedJson })
258
+ if (parsed) {
259
+ return parsed.data
260
+ }
261
+
262
+ const issues = params.schema.safeParse(parsedJson)
263
+ if (!issues.success) {
264
+ lastError = `Structured fallback failed schema validation: ${formatSchemaIssueSummary(issues.error.issues)}`
265
+ }
266
+ }
267
+
268
+ throw new Error(lastError)
269
+ }
270
+
271
+ function parseStructuredTextWithFallbacks<T>(params: {
272
+ schema: ZodSchema<T>
273
+ text: string
274
+ textFallbackParser?: (text: string) => T | null
275
+ }): T {
276
+ try {
277
+ return parseStructuredTextFallback({ schema: params.schema, text: params.text })
278
+ } catch (error) {
279
+ const parseError = error instanceof Error ? error : new Error(String(error))
280
+
281
+ if (params.textFallbackParser) {
282
+ const parsedCandidate = params.textFallbackParser(params.text)
283
+ if (parsedCandidate !== null) {
284
+ const validated = params.schema.safeParse(parsedCandidate)
285
+ if (validated.success) {
286
+ return validated.data
287
+ }
288
+
289
+ throw new Error(
290
+ `Custom text fallback failed schema validation: ${formatSchemaIssueSummary(validated.error.issues)}`,
291
+ )
292
+ }
293
+ }
294
+
295
+ throw parseError
296
+ }
297
+ }
298
+
299
+ export function createHelperModelRuntime() {
300
+ async function generateHelperText(params: GenerateHelperTextParams): Promise<string> {
301
+ const systemPrompt = resolveSystemPrompt({
302
+ explicitSystemPrompt: params.systemPrompt,
303
+ defaultSystemPrompt: params.defaultSystemPrompt,
304
+ })
305
+
306
+ if (!systemPrompt && params.messages.length === 0) {
307
+ throw new Error(`[${params.tag}] Empty helper messages`)
308
+ }
309
+
310
+ try {
311
+ const agent = params.createAgent({
312
+ ...(systemPrompt ? { instructions: systemPrompt } : {}),
313
+ ...(typeof params.temperature === 'number' ? { temperature: params.temperature } : {}),
314
+ ...(typeof params.maxOutputTokens === 'number' ? { maxOutputTokens: params.maxOutputTokens } : {}),
315
+ maxRetries: 2,
316
+ })
317
+
318
+ const result = await agent.generate({
319
+ messages: toModelMessages(params.messages),
320
+ ...(typeof params.timeoutMs === 'number' ? { timeout: params.timeoutMs } : {}),
321
+ })
322
+ const text = typeof result.text === 'string' ? result.text.trim() : ''
323
+ if (!text) {
324
+ throw new Error('Empty helper model output')
325
+ }
326
+ return text
327
+ } catch (error) {
328
+ throw formatError(params.tag, error)
329
+ }
330
+ }
331
+
332
+ async function generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T> {
333
+ const baseSystemPrompt = resolveSystemPrompt({
334
+ explicitSystemPrompt: params.systemPrompt,
335
+ defaultSystemPrompt: params.defaultSystemPrompt,
336
+ })
337
+ const systemPrompt = resolveStructuredSystemPrompt(baseSystemPrompt)
338
+
339
+ if (!baseSystemPrompt && params.messages.length === 0) {
340
+ throw new Error(`[${params.tag}] Empty helper messages`)
341
+ }
342
+
343
+ try {
344
+ const agent = params.createAgent({
345
+ output: Output.object({ schema: params.schema }),
346
+ ...(systemPrompt ? { instructions: systemPrompt } : {}),
347
+ ...(typeof params.temperature === 'number' ? { temperature: params.temperature } : {}),
348
+ ...(typeof params.maxOutputTokens === 'number' ? { maxOutputTokens: params.maxOutputTokens } : {}),
349
+ maxRetries: 2,
350
+ })
351
+
352
+ const result = await agent.generate({
353
+ messages: toModelMessages(params.messages),
354
+ ...(typeof params.timeoutMs === 'number' ? { timeout: params.timeoutMs } : {}),
355
+ })
356
+ const parsed = parseStructuredCandidate({ schema: params.schema, candidate: result.output })
357
+ if (parsed) {
358
+ return parsed.data
359
+ }
360
+
361
+ if (typeof result.text === 'string' && result.text.trim().length > 0) {
362
+ return parseStructuredTextWithFallbacks({
363
+ schema: params.schema,
364
+ text: result.text,
365
+ textFallbackParser: params.textFallbackParser,
366
+ })
367
+ }
368
+
369
+ const fallbackParsed = params.schema.safeParse(result.output)
370
+ if (!fallbackParsed.success) {
371
+ throw new Error(
372
+ `Structured output failed schema validation: ${formatSchemaIssueSummary(fallbackParsed.error.issues)}`,
373
+ )
374
+ }
375
+
376
+ return fallbackParsed.data
377
+ } catch (error) {
378
+ const structuredError = formatError(params.tag, error)
379
+ const fallbackMessages: string[] = []
380
+ const fallbackPrompts = [
381
+ systemPrompt,
382
+ ...(baseSystemPrompt && baseSystemPrompt !== systemPrompt ? [baseSystemPrompt] : []),
383
+ ]
384
+
385
+ for (const fallbackPrompt of fallbackPrompts) {
386
+ try {
387
+ const fallbackText = await generateHelperText({ ...params, systemPrompt: fallbackPrompt })
388
+ return parseStructuredTextWithFallbacks({
389
+ schema: params.schema,
390
+ text: fallbackText,
391
+ textFallbackParser: params.textFallbackParser,
392
+ })
393
+ } catch (fallbackError) {
394
+ fallbackMessages.push(fallbackError instanceof Error ? fallbackError.message : String(fallbackError))
395
+ }
396
+ }
397
+
398
+ throw new Error(`${structuredError.message}; structured_fallback=${fallbackMessages.join(' | ')}`)
399
+ }
400
+ }
401
+
402
+ return { generateHelperText, generateHelperStructured }
403
+ }
404
+
405
+ export const llmHelperService = createHelperModelRuntime()
@@ -0,0 +1,28 @@
1
+ export type IndexedRepoAgentName = string
2
+
3
+ export const REPO_SECTION_NAMES = [
4
+ 'metadata',
5
+ 'vision',
6
+ 'prd',
7
+ 'technicalMap',
8
+ 'goToMarket',
9
+ 'structure',
10
+ 'summary',
11
+ ] as const
12
+ export type RepoSectionName = (typeof REPO_SECTION_NAMES)[number]
13
+
14
+ const ALL_REPO_SECTIONS: RepoSectionName[] = [...REPO_SECTION_NAMES]
15
+
16
+ export const DEFAULT_REPO_SECTIONS_BY_AGENT: Record<IndexedRepoAgentName, RepoSectionName[]> = {
17
+ chief: [...ALL_REPO_SECTIONS],
18
+ ceo: [...ALL_REPO_SECTIONS],
19
+ cto: [...ALL_REPO_SECTIONS],
20
+ cpo: [...ALL_REPO_SECTIONS],
21
+ cmo: [...ALL_REPO_SECTIONS],
22
+ cfo: [...ALL_REPO_SECTIONS],
23
+ mentor: [...ALL_REPO_SECTIONS],
24
+ }
25
+
26
+ export function emptyAgentContextMap(): Record<IndexedRepoAgentName, string> {
27
+ return { chief: '', ceo: '', cto: '', cpo: '', cmo: '', cfo: '', mentor: '' }
28
+ }
@@ -0,0 +1,8 @@
1
+ export function mergeInstructionSections(...groups: Array<readonly string[] | undefined>): string[] | undefined {
2
+ const sections = groups
3
+ .flatMap((group) => group ?? [])
4
+ .map((section) => section.trim())
5
+ .filter((section) => section.length > 0)
6
+
7
+ return sections.length > 0 ? sections : undefined
8
+ }
@@ -0,0 +1,71 @@
1
+ export function stripMemr3Sections(text: string): string {
2
+ const normalized = text.replace(/\r\n/g, '\n')
3
+ const answerMatch = normalized.match(/\[ANSWER\]\s*\n([\s\S]*)/i)
4
+
5
+ if (answerMatch) {
6
+ return answerMatch[1]?.trim() ?? ''
7
+ }
8
+
9
+ return normalized
10
+ .replace(/```evidence\s*\n[\s\S]*?```/gi, '')
11
+ .replace(/```gaps\s*\n[\s\S]*?```/gi, '')
12
+ .replace(/\[EVIDENCE\][\s\S]*?(?=\n\[GAPS\]|\n\[ANSWER\]|\n*$)/i, '')
13
+ .replace(/\[GAPS\][\s\S]*?(?=\n\[ANSWER\]|\n*$)/i, '')
14
+ .replace(/\[ANSWER\]/i, '')
15
+ .trim()
16
+ }
17
+
18
+ const userQuestionTagsRegex = /<\/?user_question>/gi
19
+
20
+ export function stripUserQuestionTags(text: string): string {
21
+ return text.replace(userQuestionTagsRegex, '').trim()
22
+ }
23
+
24
+ const assistantMessageTagsRegex = /<\/?assistant-message\b[^>]*>/gi
25
+
26
+ export function stripAssistantMessageTags(text: string): string {
27
+ return text.replace(assistantMessageTagsRegex, '').trim()
28
+ }
29
+
30
+ const memorySectionsTokenRegex = /"sections"\s*:\s*\[/i
31
+ const memoryIdTokenRegex = /"id"\s*:\s*"memory:[^"]+"/i
32
+ const decisionHeadingRegex = /decision\s*:/i
33
+
34
+ function looksLikeMemoryJsonPreamble(text: string): boolean {
35
+ return memorySectionsTokenRegex.test(text) && memoryIdTokenRegex.test(text)
36
+ }
37
+
38
+ function getDecisionHeadingStart(text: string): number | null {
39
+ const match = decisionHeadingRegex.exec(text)
40
+ if (!match) return null
41
+ return match.index
42
+ }
43
+
44
+ export function stripLeadingMemorySectionJsonBlocks(text: string): string {
45
+ const trimmedStart = text.trimStart()
46
+ if (!trimmedStart) return trimmedStart
47
+
48
+ const decisionStart = getDecisionHeadingStart(trimmedStart)
49
+ if (decisionStart === null) {
50
+ return looksLikeMemoryJsonPreamble(trimmedStart) ? '' : trimmedStart
51
+ }
52
+
53
+ if (decisionStart === 0) {
54
+ return trimmedStart
55
+ }
56
+
57
+ const preamble = trimmedStart.slice(0, decisionStart).trim()
58
+ if (!preamble || !looksLikeMemoryJsonPreamble(preamble)) {
59
+ return trimmedStart
60
+ }
61
+
62
+ return trimmedStart.slice(decisionStart).trimStart()
63
+ }
64
+
65
+ export function sanitizeAgentOutputForMemory(text: string): string {
66
+ const withoutAssistantTags = stripAssistantMessageTags(text)
67
+ const withoutMemr3 = stripMemr3Sections(withoutAssistantTags)
68
+ const withoutUserQuestion = stripUserQuestionTags(withoutMemr3)
69
+ const withoutMemoryJson = stripLeadingMemorySectionJsonBlocks(withoutUserQuestion)
70
+ return withoutMemoryJson.trim()
71
+ }