@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
@@ -7,6 +7,8 @@ import type {
7
7
  } from 'ai'
8
8
  import type { ZodSchema } from 'zod'
9
9
 
10
+ import { isRecord, stringifyUnknown } from '../utils/string'
11
+
10
12
  export interface HelperToolLoopAgentOptions {
11
13
  instructions?: string
12
14
  maxOutputTokens?: number
@@ -47,12 +49,6 @@ export interface GenerateHelperTextParams {
47
49
  export interface GenerateHelperStructuredParams<T> extends Omit<GenerateHelperTextParams, 'tag'> {
48
50
  tag: string
49
51
  schema: ZodSchema<T>
50
- textFallbackParser?: (text: string) => T | null
51
- normalizeCandidate?: (candidate: unknown) => unknown
52
- }
53
-
54
- function isObject(value: unknown): value is Record<string, unknown> {
55
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
56
52
  }
57
53
 
58
54
  function getNumericField(value: Record<string, unknown>, key: string): number | null {
@@ -66,7 +62,7 @@ function getNumericField(value: Record<string, unknown>, key: string): number |
66
62
  }
67
63
 
68
64
  function getErrorStatus(error: unknown): number | null {
69
- if (!isObject(error)) return null
65
+ if (!isRecord(error)) return null
70
66
  return getNumericField(error, 'status') ?? getNumericField(error, 'statusCode')
71
67
  }
72
68
 
@@ -74,44 +70,11 @@ function isRateLimitError(error: unknown): boolean {
74
70
  return getErrorStatus(error) === 429
75
71
  }
76
72
 
77
- function stringifyUnknown(value: unknown, maxChars: number): string | null {
78
- if (value === null || value === undefined) return null
79
- const raw = (() => {
80
- if (typeof value === 'string') return value
81
- if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
82
- return String(value)
83
- }
84
- if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
85
- if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
86
- if (Array.isArray(value)) {
87
- try {
88
- return JSON.stringify(value)
89
- } catch {
90
- return `[array(${value.length})]`
91
- }
92
- }
93
-
94
- try {
95
- return JSON.stringify(value)
96
- } catch {
97
- const maybeName =
98
- isObject(value) && typeof (value as { constructor?: { name?: unknown } }).constructor?.name === 'string'
99
- ? (value as { constructor?: { name?: string } }).constructor?.name
100
- : 'Object'
101
- return `[object ${maybeName}]`
102
- }
103
- })()
104
-
105
- const normalized = raw.replace(/\s+/g, ' ').trim()
106
- if (!normalized) return null
107
- return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}...` : normalized
108
- }
109
-
110
73
  function formatError(tag: string, error: unknown): Error {
111
74
  const status = getErrorStatus(error)
112
75
  const rateLimited = isRateLimitError(error)
113
76
  const message = error instanceof Error ? error.message : String(error)
114
- const errorRecord = isObject(error) ? error : null
77
+ const errorRecord = isRecord(error) ? error : null
115
78
  const responseBody = errorRecord ? stringifyUnknown(errorRecord.responseBody, 600) : null
116
79
  const responseData = errorRecord ? stringifyUnknown(errorRecord.data, 600) : null
117
80
  const requestUrl = errorRecord ? stringifyUnknown(errorRecord.url, 200) : null
@@ -162,59 +125,6 @@ function formatSchemaIssueSummary(issues: Array<{ path: PropertyKey[]; message:
162
125
  .join('; ')
163
126
  }
164
127
 
165
- export function extractJsonObjectCandidates(text: string): string[] {
166
- const trimmed = text.trim()
167
- if (!trimmed) return []
168
-
169
- const candidates: string[] = [trimmed]
170
- const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)
171
- if (fencedMatch?.[1]) {
172
- candidates.push(fencedMatch[1].trim())
173
- }
174
-
175
- let start = -1
176
- let depth = 0
177
- let inString = false
178
- let escaping = false
179
-
180
- for (let index = 0; index < trimmed.length; index += 1) {
181
- const character = trimmed[index]
182
-
183
- if (escaping) {
184
- escaping = false
185
- continue
186
- }
187
-
188
- if (character === '\\') {
189
- escaping = true
190
- continue
191
- }
192
-
193
- if (character === '"') {
194
- inString = !inString
195
- continue
196
- }
197
-
198
- if (inString) continue
199
-
200
- if (character === '{') {
201
- if (depth === 0) start = index
202
- depth += 1
203
- continue
204
- }
205
-
206
- if (character === '}') {
207
- depth -= 1
208
- if (depth === 0 && start >= 0) {
209
- candidates.push(trimmed.slice(start, index + 1))
210
- start = -1
211
- }
212
- }
213
- }
214
-
215
- return [...new Set(candidates.filter((candidate) => candidate.length > 0))]
216
- }
217
-
218
128
  function parseStructuredCandidate<T>(params: {
219
129
  schema: ZodSchema<T>
220
130
  candidate: unknown
@@ -224,7 +134,7 @@ function parseStructuredCandidate<T>(params: {
224
134
  return { data: direct.data, source: 'root' }
225
135
  }
226
136
 
227
- if (isObject(params.candidate)) {
137
+ if (isRecord(params.candidate)) {
228
138
  for (const key of ['output', 'result', 'data'] as const) {
229
139
  const nested = params.candidate[key]
230
140
  const nestedParsed = params.schema.safeParse(nested)
@@ -237,66 +147,6 @@ function parseStructuredCandidate<T>(params: {
237
147
  return null
238
148
  }
239
149
 
240
- function parseStructuredTextFallback<T>(params: { schema: ZodSchema<T>; text: string }): T {
241
- const candidates = extractJsonObjectCandidates(params.text)
242
- if (candidates.length === 0) {
243
- throw new Error('Structured fallback did not contain a JSON object candidate.')
244
- }
245
-
246
- let lastError = 'Structured fallback could not be parsed.'
247
-
248
- for (const candidateText of candidates) {
249
- let parsedJson: unknown
250
-
251
- try {
252
- parsedJson = JSON.parse(candidateText) as unknown
253
- } catch {
254
- lastError = 'Structured fallback JSON parsing failed.'
255
- continue
256
- }
257
-
258
- const parsed = parseStructuredCandidate({ schema: params.schema, candidate: parsedJson })
259
- if (parsed) {
260
- return parsed.data
261
- }
262
-
263
- const issues = params.schema.safeParse(parsedJson)
264
- if (!issues.success) {
265
- lastError = `Structured fallback failed schema validation: ${formatSchemaIssueSummary(issues.error.issues)}`
266
- }
267
- }
268
-
269
- throw new Error(lastError)
270
- }
271
-
272
- function parseStructuredTextWithFallbacks<T>(params: {
273
- schema: ZodSchema<T>
274
- text: string
275
- textFallbackParser?: (text: string) => T | null
276
- }): T {
277
- try {
278
- return parseStructuredTextFallback({ schema: params.schema, text: params.text })
279
- } catch (error) {
280
- const parseError = error instanceof Error ? error : new Error(String(error))
281
-
282
- if (params.textFallbackParser) {
283
- const parsedCandidate = params.textFallbackParser(params.text)
284
- if (parsedCandidate !== null) {
285
- const validated = params.schema.safeParse(parsedCandidate)
286
- if (validated.success) {
287
- return validated.data
288
- }
289
-
290
- throw new Error(
291
- `Custom text fallback failed schema validation: ${formatSchemaIssueSummary(validated.error.issues)}`,
292
- )
293
- }
294
- }
295
-
296
- throw parseError
297
- }
298
- }
299
-
300
150
  export function createHelperModelRuntime() {
301
151
  async function generateHelperText(params: GenerateHelperTextParams): Promise<string> {
302
152
  const systemPrompt = resolveSystemPrompt({
@@ -359,52 +209,16 @@ export function createHelperModelRuntime() {
359
209
  return parsed.data
360
210
  }
361
211
 
362
- if (params.normalizeCandidate && isObject(result.output)) {
363
- const normalized = params.normalizeCandidate(result.output)
364
- const normalizedParsed = parseStructuredCandidate({ schema: params.schema, candidate: normalized })
365
- if (normalizedParsed) {
366
- return normalizedParsed.data
367
- }
368
- }
369
-
370
- if (typeof result.text === 'string' && result.text.trim().length > 0) {
371
- return parseStructuredTextWithFallbacks({
372
- schema: params.schema,
373
- text: result.text,
374
- textFallbackParser: params.textFallbackParser,
375
- })
376
- }
377
-
378
- const fallbackParsed = params.schema.safeParse(result.output)
379
- if (!fallbackParsed.success) {
212
+ const directParsed = params.schema.safeParse(result.output)
213
+ if (!directParsed.success) {
380
214
  throw new Error(
381
- `Structured output failed schema validation: ${formatSchemaIssueSummary(fallbackParsed.error.issues)}`,
215
+ `Structured output failed schema validation: ${formatSchemaIssueSummary(directParsed.error.issues)}`,
382
216
  )
383
217
  }
384
218
 
385
- return fallbackParsed.data
219
+ return directParsed.data
386
220
  } catch (error) {
387
- const structuredError = formatError(params.tag, error)
388
- const fallbackMessages: string[] = []
389
- const fallbackPrompts = [
390
- systemPrompt,
391
- ...(baseSystemPrompt && baseSystemPrompt !== systemPrompt ? [baseSystemPrompt] : []),
392
- ]
393
-
394
- for (const fallbackPrompt of fallbackPrompts) {
395
- try {
396
- const fallbackText = await generateHelperText({ ...params, systemPrompt: fallbackPrompt })
397
- return parseStructuredTextWithFallbacks({
398
- schema: params.schema,
399
- text: fallbackText,
400
- textFallbackParser: params.textFallbackParser,
401
- })
402
- } catch (fallbackError) {
403
- fallbackMessages.push(fallbackError instanceof Error ? fallbackError.message : String(fallbackError))
404
- }
405
- }
406
-
407
- throw new Error(`${structuredError.message}; structured_fallback=${fallbackMessages.join(' | ')}`)
221
+ throw formatError(params.tag, error)
408
222
  }
409
223
  }
410
224
 
@@ -6,6 +6,7 @@ export * from './chat-request-routing'
6
6
  export * from './chat-run-registry'
7
7
  export * from './context-compaction'
8
8
  export * from './execution-plan'
9
+ export * from './graph-designer'
9
10
  export * from './helper-model'
10
11
  export * from './indexed-repositories-policy'
11
12
  export * from './instruction-sections'
@@ -13,6 +14,7 @@ export * from './memory-block'
13
14
  export * from './memory-digest-policy'
14
15
  export * from './memory-scope'
15
16
  export * from './llm-content'
17
+ export * from './plugin-resolution'
16
18
  export * from './plugin-types'
17
19
  export * from './runtime-config'
18
20
  export * from './runtime-extensions'
@@ -23,4 +25,15 @@ export * from './team-consultation-prompts'
23
25
  export * from './turn-lifecycle'
24
26
  export * from './workstream-chat-helpers'
25
27
  export * from './workstream-routing-policy'
26
- export * from './workstream-state'
28
+ export {
29
+ WorkstreamStateSchema,
30
+ type WorkstreamState,
31
+ type WorkstreamStateDelta,
32
+ StructuredWorkstreamStateDeltaSchema,
33
+ type StructuredWorkstreamStateDelta,
34
+ createEmptyStructuredWorkstreamStateDelta,
35
+ parseStructuredWorkstreamStateDelta,
36
+ StructuredCompactionOutputSchema,
37
+ type CompactionOutput,
38
+ createEmptyWorkstreamState,
39
+ } from './workstream-state'
@@ -3,7 +3,7 @@ export function stripMemr3Sections(text: string): string {
3
3
  const answerMatch = normalized.match(/\[ANSWER\]\s*\n([\s\S]*)/i)
4
4
 
5
5
  if (answerMatch) {
6
- return answerMatch[1]?.trim() ?? ''
6
+ return answerMatch[1].trim()
7
7
  }
8
8
 
9
9
  return normalized
@@ -1,4 +1,7 @@
1
+ import { z } from 'zod'
2
+
1
3
  import { agentDisplayNames, agentShortDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
4
+ import { compactWhitespace } from '../utils/string'
2
5
 
3
6
  function escapeRegex(value: string): string {
4
7
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
@@ -32,11 +35,9 @@ function createLabelPrefixRegex(labelRoles: readonly string[]): RegExp | null {
32
35
  return new RegExp(`^(?:${aliases.map((role) => escapeRegex(role)).join('|')})\\s*:\\s*`, 'i')
33
36
  }
34
37
 
35
- export interface MemoryBlockEntry {
36
- role: string
37
- content: string
38
- timestamp: string
39
- }
38
+ const MemoryBlockEntrySchema = z.object({ role: z.string(), content: z.string(), timestamp: z.string() })
39
+
40
+ export type MemoryBlockEntry = z.infer<typeof MemoryBlockEntrySchema>
40
41
 
41
42
  export interface MemoryBlockRuntime {
42
43
  normalizeMemoryBlockEntry: (entry: string) => string
@@ -173,8 +174,7 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
173
174
  )
174
175
  .filter(Boolean)
175
176
 
176
- const candidate = normalizedLines.join(' ').trim()
177
- const collapsed = candidate.replace(/\s+/g, ' ').trim()
177
+ const collapsed = compactWhitespace(normalizedLines.join(' '))
178
178
  if (!collapsed) return ''
179
179
  return collapsed
180
180
  }
@@ -184,9 +184,8 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
184
184
  const trimmed = raw.trim()
185
185
  if (!trimmed.startsWith('[')) return []
186
186
  try {
187
- const parsed: unknown = JSON.parse(trimmed)
188
- if (!Array.isArray(parsed)) return []
189
- return parsed as MemoryBlockEntry[]
187
+ const parsed = z.array(MemoryBlockEntrySchema).safeParse(JSON.parse(trimmed))
188
+ return parsed.success ? parsed.data : []
190
189
  } catch {
191
190
  return []
192
191
  }
@@ -213,8 +212,8 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
213
212
  .some((line) => {
214
213
  const match = line.match(/^([a-z][a-z0-9_ -]*)\s*:\s*(.+)$/i)
215
214
  if (!match) return false
216
- const rowRole = normalizeMemoryBlockRole(match[1] ?? '')
217
- const rowContent = match[2] ?? ''
215
+ const rowRole = normalizeMemoryBlockRole(match[1])
216
+ const rowContent = match[2]
218
217
  if (rowRole !== roleLower) return false
219
218
  return normalizeMemoryBlockEntry(rowContent) === normalizedTarget
220
219
  })
@@ -1,3 +1,11 @@
1
+ import { clampImportance, compactWhitespace } from '../utils/string'
2
+
3
+ const SCORE_WEIGHTS = {
4
+ durability: { core: 0.35, standard: 0.2, weak: 0.05 },
5
+ type: { decision: 0.25, fact: 0.18, preference: 0.15, default: 0.1 },
6
+ maxContentLength: 120,
7
+ } as const
8
+
1
9
  interface MemoryFactInput {
2
10
  content: string
3
11
  confidence: number
@@ -99,11 +107,6 @@ interface MemoryActionPlan<TRelation extends string = string> {
99
107
  relations: MemoryActionRelation<TRelation>[]
100
108
  }
101
109
 
102
- function clampImportance(value: number): number {
103
- if (!Number.isFinite(value)) return 0
104
- return Math.max(0, Math.min(1, value))
105
- }
106
-
107
110
  function normalizeMemoryKey(text: string): string {
108
111
  return text
109
112
  .toLowerCase()
@@ -117,9 +120,22 @@ function scoreFact<T extends MemoryFactInput>(fact: T): number {
117
120
  const durability = fact.durability ?? 'standard'
118
121
  const type = fact.type ?? 'fact'
119
122
 
120
- const durabilityWeight = durability === 'core' ? 0.35 : durability === 'standard' ? 0.2 : 0.05
121
- const typeWeight = type === 'decision' ? 0.25 : type === 'fact' ? 0.18 : 0.1
122
- const lengthWeight = Math.min(fact.content.length, 120) / 120 / 10
123
+ const durabilityWeight =
124
+ durability === 'core'
125
+ ? SCORE_WEIGHTS.durability.core
126
+ : durability === 'standard'
127
+ ? SCORE_WEIGHTS.durability.standard
128
+ : SCORE_WEIGHTS.durability.weak
129
+ const typeWeight =
130
+ type === 'decision'
131
+ ? SCORE_WEIGHTS.type.decision
132
+ : type === 'fact'
133
+ ? SCORE_WEIGHTS.type.fact
134
+ : type === 'preference'
135
+ ? SCORE_WEIGHTS.type.preference
136
+ : SCORE_WEIGHTS.type.default
137
+ const lengthWeight =
138
+ Math.min(fact.content.length, SCORE_WEIGHTS.maxContentLength) / SCORE_WEIGHTS.maxContentLength / 10
123
139
 
124
140
  return confidence + durabilityWeight + typeWeight + lengthWeight
125
141
  }
@@ -160,7 +176,7 @@ export function postProcessMemoryFacts<T extends MemoryFactInput>(
160
176
  const deduped = new Map<string, T>()
161
177
 
162
178
  for (const fact of rawFacts) {
163
- const content = typeof fact.content === 'string' ? fact.content.replace(/\s+/g, ' ').trim() : ''
179
+ const content = typeof fact.content === 'string' ? compactWhitespace(fact.content) : ''
164
180
  if (!content || content.length < minChars || content.length > maxChars) continue
165
181
  const normalizedFact = { ...fact, content }
166
182
  const key = normalizeFactForDedupe(content)
@@ -464,7 +480,7 @@ export function createMemoryActionPlan<TRelation extends string = string>(params
464
480
  }
465
481
 
466
482
  for (const [index, item] of params.updates.memory.entries()) {
467
- const text = typeof item.text === 'string' ? item.text.replace(/\s+/g, ' ').trim() : ''
483
+ const text = typeof item.text === 'string' ? compactWhitespace(item.text) : ''
468
484
  const itemId = typeof item.id === 'string' ? item.id.trim() : ''
469
485
 
470
486
  switch (item.event) {
@@ -0,0 +1,35 @@
1
+ import { pluginRuntime } from '../config/agent-defaults'
2
+ import { getRuntimeAdapters } from './runtime-extensions'
3
+
4
+ export function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
5
+ let current: unknown = pluginRuntime
6
+ let owner: unknown = undefined
7
+ for (const key of path) {
8
+ if (current === null || current === undefined || typeof current !== 'object') return undefined
9
+ owner = current
10
+ current = (current as Record<string, unknown>)[key]
11
+ }
12
+ if (typeof current !== 'function') {
13
+ return undefined
14
+ }
15
+
16
+ return owner && typeof owner === 'object'
17
+ ? (current as (...args: unknown[]) => unknown).bind(owner)
18
+ : (current as (...args: unknown[]) => unknown)
19
+ }
20
+
21
+ export async function buildIndexedRepositoriesContext(
22
+ organizationId: string,
23
+ ): Promise<{ provideRepoTool: boolean; defaultSectionsByAgent: Record<string, unknown>; context: string }> {
24
+ const buildContext = getRuntimeAdapters().workstream?.buildIndexedRepositoriesContext
25
+ if (!buildContext) {
26
+ return { provideRepoTool: false, defaultSectionsByAgent: {}, context: '' }
27
+ }
28
+
29
+ const result = await buildContext(organizationId)
30
+ return {
31
+ provideRepoTool: result.provideRepoTool,
32
+ defaultSectionsByAgent: result.defaultSectionsByAgent,
33
+ context: result.context ?? '',
34
+ }
35
+ }
@@ -1,10 +1,82 @@
1
+ import type {
2
+ CarryForwardPolicy,
3
+ CycleSchedule,
4
+ PlanDraft,
5
+ PlanNodeResult,
6
+ PlanNodeSpec,
7
+ PlanScheduleSpec,
8
+ SignalDeclaration,
9
+ } from '@lota-sdk/shared'
10
+
11
+ import type { RecordIdRef } from '../db/record-id'
12
+
13
+ export interface PluginContextEnricher {
14
+ domain: string
15
+ enrich(params: {
16
+ objective: string
17
+ organizationId: string
18
+ }): Promise<{ data: Record<string, unknown>; confidence: number }>
19
+ }
20
+
1
21
  export interface LotaPluginContributions {
2
22
  envKeys: readonly string[]
3
23
  schemaFiles: readonly (string | URL)[]
24
+ signals?: readonly SignalDeclaration[]
25
+ }
26
+
27
+ export interface PluginNodeExecutorContext {
28
+ organizationId: string
29
+ workstreamId: string
30
+ planId: string
31
+ nodeId: string
32
+ userId?: RecordIdRef
33
+ userName?: string
34
+ }
35
+
36
+ export interface PluginNodeExecutionParams {
37
+ operation: string
38
+ nodeSpec: PlanNodeSpec
39
+ inputs: Record<string, unknown>
40
+ context: PluginNodeExecutorContext
41
+ }
42
+
43
+ export interface PluginNodeExecutor {
44
+ supportedOperations: readonly string[]
45
+ executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult>
46
+ }
47
+
48
+ export interface SystemNodeExecutor {
49
+ supportedOperations: readonly string[]
50
+ executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult>
51
+ }
52
+
53
+ export interface PlaybookContribution {
54
+ name: string
55
+ description: string
56
+ tags: string[]
57
+ draft: PlanDraft
58
+ schedule?: PlanScheduleSpec
59
+ cycleSchedule?: CycleSchedule
60
+ carryForwardPolicy?: CarryForwardPolicy
61
+ }
62
+
63
+ export interface PlaybookContributor {
64
+ playbooks: readonly PlaybookContribution[]
65
+ }
66
+
67
+ export interface PluginDomainAgentDefinition {
68
+ agentId: string
69
+ displayName: string
70
+ capabilities: readonly string[]
4
71
  }
5
72
 
6
- export interface LotaPlugin<TServices = unknown, TTools = unknown> {
73
+ export interface LotaPlugin<TServices = Record<string, unknown>, TTools = Record<string, unknown>> {
7
74
  services: TServices
75
+ nodeExecutor?: PluginNodeExecutor
8
76
  tools?: TTools
9
77
  contributions: LotaPluginContributions
78
+ playbookContributor?: PlaybookContributor
79
+ domainAgents?: readonly PluginDomainAgentDefinition[]
80
+ contextEnrichers?: readonly PluginContextEnricher[]
81
+ onSignal?: (signal: string, payload: unknown, source: string) => Promise<void> | void
10
82
  }
@@ -11,7 +11,7 @@ interface ScopedRetrievalResult<TCandidate> {
11
11
  export async function executeScopedRetrieval<TCandidate>(
12
12
  tasks: ScopedRetrievalTask<TCandidate>[],
13
13
  ): Promise<ScopedRetrievalResult<TCandidate>[]> {
14
- return await Promise.all(tasks.map(async (task) => ({ scopeTag: task.scopeTag, candidates: await task.retrieve() })))
14
+ return Promise.all(tasks.map(async (task) => ({ scopeTag: task.scopeTag, candidates: await task.retrieve() })))
15
15
  }
16
16
 
17
17
  export function countScopedRetrievalCandidates<TCandidate>(scoped: ScopedRetrievalResult<TCandidate>[]): number {
@@ -1,17 +1,17 @@
1
1
  import { z } from 'zod'
2
2
 
3
3
  import type { CoreWorkstreamProfile } from '../config/agent-defaults'
4
+ import type { AgentFactory, AgentRuntimeConfigProvider, AgentToolBuilder } from '../config/agent-types'
4
5
  import type { LotaWorkstreamConfig, WorkstreamBootstrapWelcomeConfig } from '../config/workstream-defaults'
5
- import type { LotaPlugin } from './plugin-types'
6
+ import type { NotificationService } from '../services/notification.service'
7
+ import { isRecord } from '../utils/string'
8
+ import type { GraphDesigner } from './graph-designer'
9
+ import type { LotaPlugin, SystemNodeExecutor } from './plugin-types'
6
10
  import type { LotaRuntimeAdapters, LotaRuntimeTurnHooks } from './runtime-extensions'
7
11
  import type { LotaRuntimeWorkerExtensions } from './runtime-worker-registry'
8
12
 
9
13
  const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
10
14
 
11
- type LotaAgentFactoryRegistry = Record<string, (...args: unknown[]) => unknown>
12
-
13
- const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null
14
-
15
15
  function isStringOrUrl(value: unknown): value is string | URL {
16
16
  return typeof value === 'string' || value instanceof URL
17
17
  }
@@ -24,7 +24,7 @@ function isStringRecord(value: unknown): value is Record<string, string> {
24
24
  return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string')
25
25
  }
26
26
 
27
- function isAgentFactoryRegistry(value: unknown): value is LotaAgentFactoryRegistry {
27
+ function isAgentFactoryRegistry(value: unknown): value is AgentFactory {
28
28
  return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'function')
29
29
  }
30
30
 
@@ -32,10 +32,22 @@ function isPluginRuntimeRecord(value: unknown): value is Record<string, LotaPlug
32
32
  return isRecord(value)
33
33
  }
34
34
 
35
+ function isSystemExecutorRecord(value: unknown): value is Record<string, SystemNodeExecutor> {
36
+ return isRecord(value)
37
+ }
38
+
35
39
  function isToolProviderRecord(value: unknown): value is Record<string, unknown> {
36
40
  return isRecord(value)
37
41
  }
38
42
 
43
+ function isNotificationService(value: unknown): value is NotificationService {
44
+ return isRecord(value) && isFunction(value.notify) && isFunction(value.remind) && isFunction(value.escalate)
45
+ }
46
+
47
+ function isGraphDesigner(value: unknown): value is GraphDesigner {
48
+ return isRecord(value) && isFunction(value.designGraph)
49
+ }
50
+
39
51
  function isWorkerExtensionRecord(value: unknown): value is LotaRuntimeWorkerExtensions {
40
52
  if (!isRecord(value)) return false
41
53
 
@@ -85,15 +97,13 @@ const agentsConfigSchema = z
85
97
  })
86
98
  .optional(),
87
99
  createAgent: z
88
- .custom<LotaAgentFactoryRegistry>(isAgentFactoryRegistry, {
89
- error: 'agents.createAgent must be a function registry',
90
- })
100
+ .custom<AgentFactory>(isAgentFactoryRegistry, { error: 'agents.createAgent must be a function registry' })
91
101
  .optional(),
92
102
  buildAgentTools: z
93
- .custom<(...args: unknown[]) => unknown>(isFunction, { error: 'agents.buildAgentTools must be a function' })
103
+ .custom<AgentToolBuilder>(isFunction, { error: 'agents.buildAgentTools must be a function' })
94
104
  .optional(),
95
105
  getAgentRuntimeConfig: z
96
- .custom<(...args: unknown[]) => unknown>(isFunction, { error: 'agents.getAgentRuntimeConfig must be a function' })
106
+ .custom<AgentRuntimeConfigProvider>(isFunction, { error: 'agents.getAgentRuntimeConfig must be a function' })
97
107
  .optional(),
98
108
  })
99
109
  .superRefine((value, ctx) => {
@@ -173,8 +183,11 @@ export const LotaRuntimeConfigSchema = z.object({
173
183
  extraSchemaFiles: z.array(z.custom<string | URL>(isStringOrUrl)).optional(),
174
184
  extraWorkers: z.custom<LotaRuntimeWorkerExtensions>(isWorkerExtensionRecord).optional(),
175
185
  pluginRuntime: z.custom<Record<string, LotaPlugin>>(isPluginRuntimeRecord).optional(),
186
+ systemExecutors: z.custom<Record<string, SystemNodeExecutor>>(isSystemExecutorRecord).optional(),
187
+ notificationService: z.custom<NotificationService>(isNotificationService).optional(),
176
188
  runtimeAdapters: z.custom<LotaRuntimeAdapters>(isRecord).optional(),
177
189
  turnHooks: z.custom<LotaRuntimeTurnHooks>(isRecord).optional(),
190
+ graphDesigner: z.custom<GraphDesigner>(isGraphDesigner).optional(),
178
191
  })
179
192
 
180
193
  export type LotaRuntimeConfig = z.input<typeof LotaRuntimeConfigSchema>
@@ -235,4 +248,4 @@ export function parseWorkerBootstrapEnv(env: Record<string, string | undefined>)
235
248
  return WORKER_BOOTSTRAP_ENV_SCHEMA.parse(env)
236
249
  }
237
250
 
238
- export type { LotaAgentFactoryRegistry, LotaWorkstreamConfig }
251
+ export type { LotaWorkstreamConfig }