@lota-sdk/core 0.1.15 → 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 (138) 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 +8 -7
  9. package/src/ai/definitions.ts +80 -2
  10. package/src/ai/index.ts +0 -2
  11. package/src/bifrost/bifrost.ts +2 -7
  12. package/src/config/agent-defaults.ts +31 -21
  13. package/src/config/agent-types.ts +11 -0
  14. package/src/config/constants.ts +2 -14
  15. package/src/config/debug-logger.ts +5 -1
  16. package/src/config/index.ts +3 -0
  17. package/src/config/model-constants.ts +16 -34
  18. package/src/config/search.ts +1 -15
  19. package/src/create-runtime.ts +244 -178
  20. package/src/db/cursor-pagination.ts +3 -6
  21. package/src/db/index.ts +2 -0
  22. package/src/db/memory-store.rows.ts +7 -7
  23. package/src/db/memory-store.ts +14 -18
  24. package/src/db/memory.ts +13 -13
  25. package/src/db/service.ts +153 -79
  26. package/src/db/startup.ts +6 -10
  27. package/src/db/surreal-mutation.ts +43 -0
  28. package/src/db/tables.ts +7 -0
  29. package/src/db/workstream-message-row.ts +15 -0
  30. package/src/embeddings/provider.ts +1 -1
  31. package/src/queues/context-compaction.queue.ts +15 -46
  32. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  33. package/src/queues/index.ts +3 -0
  34. package/src/queues/memory-consolidation.queue.ts +16 -51
  35. package/src/queues/plan-scheduler.queue.ts +97 -0
  36. package/src/queues/post-chat-memory.queue.ts +15 -56
  37. package/src/queues/queue-factory.ts +100 -0
  38. package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
  39. package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
  40. package/src/queues/skill-extraction.queue.ts +15 -47
  41. package/src/queues/workstream-title-generation.queue.ts +15 -47
  42. package/src/redis/connection.ts +6 -0
  43. package/src/redis/index.ts +1 -1
  44. package/src/redis/stream-context.ts +11 -0
  45. package/src/runtime/agent-runtime-policy.ts +106 -21
  46. package/src/runtime/approval-continuation.ts +12 -6
  47. package/src/runtime/context-compaction-runtime.ts +1 -1
  48. package/src/runtime/context-compaction.ts +22 -60
  49. package/src/runtime/execution-plan.ts +22 -18
  50. package/src/runtime/graph-designer.ts +15 -0
  51. package/src/runtime/helper-model.ts +9 -197
  52. package/src/runtime/index.ts +2 -0
  53. package/src/runtime/llm-content.ts +1 -1
  54. package/src/runtime/memory-block.ts +9 -11
  55. package/src/runtime/memory-pipeline.ts +6 -9
  56. package/src/runtime/plugin-resolution.ts +35 -0
  57. package/src/runtime/plugin-types.ts +72 -0
  58. package/src/runtime/retrieval-adapters.ts +1 -1
  59. package/src/runtime/runtime-config.ts +25 -12
  60. package/src/runtime/runtime-extensions.ts +2 -2
  61. package/src/runtime/runtime-worker-registry.ts +6 -0
  62. package/src/runtime/team-consultation-orchestrator.ts +45 -28
  63. package/src/runtime/team-consultation-prompts.ts +11 -2
  64. package/src/runtime/title-helpers.ts +2 -4
  65. package/src/runtime/workstream-chat-helpers.ts +1 -1
  66. package/src/services/adaptive-playbook.service.ts +152 -0
  67. package/src/services/agent-executor.service.ts +293 -0
  68. package/src/services/artifact-provenance.service.ts +172 -0
  69. package/src/services/attachment.service.ts +6 -11
  70. package/src/services/context-compaction.service.ts +72 -55
  71. package/src/services/context-enrichment.service.ts +33 -0
  72. package/src/services/coordination-registry.service.ts +117 -0
  73. package/src/services/document-chunk.service.ts +1 -1
  74. package/src/services/domain-agent-executor.service.ts +71 -0
  75. package/src/services/execution-plan.service.ts +269 -50
  76. package/src/services/feedback-loop.service.ts +96 -0
  77. package/src/services/global-orchestrator.service.ts +148 -0
  78. package/src/services/index.ts +26 -0
  79. package/src/services/institutional-memory.service.ts +145 -0
  80. package/src/services/learned-skill.service.ts +24 -5
  81. package/src/services/memory-assessment.service.ts +3 -2
  82. package/src/services/memory-utils.ts +3 -8
  83. package/src/services/memory.service.ts +42 -59
  84. package/src/services/monitoring-window.service.ts +86 -0
  85. package/src/services/mutating-approval.service.ts +1 -1
  86. package/src/services/node-workspace.service.ts +155 -0
  87. package/src/services/notification.service.ts +39 -0
  88. package/src/services/organization-member.service.ts +11 -4
  89. package/src/services/organization.service.ts +5 -5
  90. package/src/services/ownership-dispatcher.service.ts +403 -0
  91. package/src/services/plan-approval.service.ts +1 -1
  92. package/src/services/plan-builder.service.ts +1 -0
  93. package/src/services/plan-checkpoint.service.ts +30 -2
  94. package/src/services/plan-compiler.service.ts +5 -0
  95. package/src/services/plan-coordination.service.ts +152 -0
  96. package/src/services/plan-cycle.service.ts +284 -0
  97. package/src/services/plan-deadline.service.ts +287 -0
  98. package/src/services/plan-executor.service.ts +384 -40
  99. package/src/services/plan-run.service.ts +41 -7
  100. package/src/services/plan-scheduler.service.ts +240 -0
  101. package/src/services/plan-template.service.ts +117 -0
  102. package/src/services/plan-validator.service.ts +84 -2
  103. package/src/services/plan-workspace.service.ts +83 -0
  104. package/src/services/playbook-registry.service.ts +67 -0
  105. package/src/services/plugin-executor.service.ts +103 -0
  106. package/src/services/quality-metrics.service.ts +132 -0
  107. package/src/services/recent-activity.service.ts +27 -31
  108. package/src/services/skill-resolver.service.ts +19 -0
  109. package/src/services/system-executor.service.ts +105 -0
  110. package/src/services/workstream-message.service.ts +12 -34
  111. package/src/services/workstream-plan-registry.service.ts +22 -0
  112. package/src/services/workstream-title.service.ts +3 -1
  113. package/src/services/workstream-turn-preparation.service.ts +34 -66
  114. package/src/services/workstream.service.ts +33 -55
  115. package/src/services/workstream.types.ts +9 -9
  116. package/src/services/write-intent-validator.service.ts +81 -0
  117. package/src/storage/attachment-parser.ts +1 -1
  118. package/src/storage/attachment-utils.ts +1 -1
  119. package/src/storage/generated-document-storage.service.ts +3 -2
  120. package/src/system-agents/delegated-agent-factory.ts +2 -0
  121. package/src/tools/execution-plan.tool.ts +17 -23
  122. package/src/tools/index.ts +0 -1
  123. package/src/tools/team-think.tool.ts +6 -4
  124. package/src/utils/async.ts +2 -1
  125. package/src/utils/date-time.ts +4 -32
  126. package/src/utils/env.ts +8 -0
  127. package/src/utils/errors.ts +42 -10
  128. package/src/utils/index.ts +9 -0
  129. package/src/utils/string.ts +114 -1
  130. package/src/workers/index.ts +1 -0
  131. package/src/workers/regular-chat-memory-digest.runner.ts +2 -2
  132. package/src/workers/skill-extraction.runner.ts +1 -1
  133. package/src/workers/utils/file-section-chunker.ts +2 -1
  134. package/src/workers/utils/repomix-file-sections.ts +2 -2
  135. package/src/workers/utils/sandbox-error.ts +11 -2
  136. package/src/workers/utils/workstream-message-query.ts +11 -20
  137. package/src/workers/worker-utils.ts +2 -2
  138. package/src/tools/log-hello-world.tool.ts +0 -17
@@ -2,16 +2,18 @@ import type { SerializableExecutionPlan } from '@lota-sdk/shared'
2
2
 
3
3
  const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
4
4
  - Before doing multi-step work, create a contract-driven execution plan instead of tracking steps only in prose.
5
+ - A workstream may have multiple active execution plans. Review all plans before creating new ones.
5
6
  - Plans are graph-capable workflow contracts. Every execution node must define objective, instructions, deliverables, success criteria, completion checks, retry policy, failure policy, and tool/context policy.
6
7
  - The runtime executor owns lifecycle truth. Do not claim that a node is complete until submitExecutionNodeResult succeeds.
7
- - Use execution-plan tools to create, replace, inspect, submit node results, and resume runs.
8
- - Treat the active execution run in <execution-plan-state> as authoritative. Do not mutate run or node status in prose.
8
+ - Use execution-plan tools to create, replace, inspect, and resume runs.
9
+ - Visible workstream agents do not manually submit node results; dispatched execution nodes are completed by the runtime executor.
10
+ - Treat the active execution runs in <execution-plan-state> as authoritative. Do not mutate run or node status in prose.
9
11
  - Work only on nodes that are active or explicitly ready for your executor. If a node is awaiting human input or approval, stop and let the runtime resume it.
10
12
  - If the graph, contracts, or success criteria materially change, replace the plan instead of silently drifting.
11
13
  </execution-plan-protocol>`
12
14
 
13
- function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | undefined): string | undefined {
14
- if (!plan) return undefined
15
+ function formatExecutionPlansForPrompt(plans: SerializableExecutionPlan[]): string | undefined {
16
+ if (plans.length === 0) return undefined
15
17
 
16
18
  const payload = {
17
19
  policy: {
@@ -21,46 +23,48 @@ function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | u
21
23
  artifactsAreFirstClassOutputs: true,
22
24
  checkpointRecoveryEnabled: true,
23
25
  },
24
- plan,
26
+ activePlans: plans,
27
+ planCount: plans.length,
25
28
  }
26
29
 
27
30
  return ['<execution-plan-state>', JSON.stringify(payload, null, 2), '</execution-plan-state>'].join('\n')
28
31
  }
29
32
 
30
33
  export function buildExecutionPlanInstructionSections(
31
- plan: SerializableExecutionPlan | null | undefined,
34
+ plans: SerializableExecutionPlan[] | null | undefined,
32
35
  ): string[] | undefined {
36
+ const normalized = plans ?? []
33
37
  const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
34
- const executionPlanStateSection = formatExecutionPlanForPrompt(plan)
35
- if (executionPlanStateSection) {
36
- sections.push(executionPlanStateSection)
38
+ const stateSection = formatExecutionPlansForPrompt(normalized)
39
+ if (stateSection) {
40
+ sections.push(stateSection)
37
41
  }
38
42
  return sections
39
43
  }
40
44
 
41
45
  export function createExecutionPlanInstructionSectionCache(params: {
42
46
  disabled?: boolean
43
- loadPlan: () => Promise<SerializableExecutionPlan | null | undefined>
47
+ loadPlans: () => Promise<SerializableExecutionPlan[]>
44
48
  }) {
45
- let planPromise: Promise<SerializableExecutionPlan | null | undefined> | null = null
49
+ let plansPromise: Promise<SerializableExecutionPlan[]> | null = null
46
50
  let sectionsPromise: Promise<string[] | undefined> | null = null
47
51
 
48
52
  return {
49
53
  invalidate() {
50
- planPromise = null
54
+ plansPromise = null
51
55
  sectionsPromise = null
52
56
  },
53
- async getPlan(): Promise<SerializableExecutionPlan | null | undefined> {
54
- if (params.disabled) return undefined
57
+ async getPlans(): Promise<SerializableExecutionPlan[]> {
58
+ if (params.disabled) return []
55
59
 
56
- planPromise ??= params.loadPlan()
57
- return await planPromise
60
+ plansPromise ??= params.loadPlans()
61
+ return plansPromise
58
62
  },
59
63
  async getSections(): Promise<string[] | undefined> {
60
64
  if (params.disabled) return undefined
61
65
 
62
- sectionsPromise ??= this.getPlan().then((plan) => buildExecutionPlanInstructionSections(plan))
63
- return await sectionsPromise
66
+ sectionsPromise ??= this.getPlans().then((plans) => buildExecutionPlanInstructionSections(plans))
67
+ return sectionsPromise
64
68
  },
65
69
  }
66
70
  }
@@ -0,0 +1,15 @@
1
+ import type { GraphDesignRequest, GraphDesignResponse } from '@lota-sdk/shared'
2
+
3
+ export interface GraphDesigner {
4
+ designGraph(request: GraphDesignRequest): Promise<GraphDesignResponse>
5
+ }
6
+
7
+ let _graphDesigner: GraphDesigner | null = null
8
+
9
+ export function configureGraphDesigner(designer: GraphDesigner): void {
10
+ _graphDesigner = designer
11
+ }
12
+
13
+ export function getGraphDesigner(): GraphDesigner | null {
14
+ return _graphDesigner
15
+ }
@@ -7,7 +7,7 @@ import type {
7
7
  } from 'ai'
8
8
  import type { ZodSchema } from 'zod'
9
9
 
10
- import { compactWhitespace } from '../utils/string'
10
+ import { isRecord, stringifyUnknown } from '../utils/string'
11
11
 
12
12
  export interface HelperToolLoopAgentOptions {
13
13
  instructions?: string
@@ -49,12 +49,6 @@ export interface GenerateHelperTextParams {
49
49
  export interface GenerateHelperStructuredParams<T> extends Omit<GenerateHelperTextParams, 'tag'> {
50
50
  tag: string
51
51
  schema: ZodSchema<T>
52
- textFallbackParser?: (text: string) => T | null
53
- normalizeCandidate?: (candidate: unknown) => unknown
54
- }
55
-
56
- function isObject(value: unknown): value is Record<string, unknown> {
57
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
58
52
  }
59
53
 
60
54
  function getNumericField(value: Record<string, unknown>, key: string): number | null {
@@ -68,7 +62,7 @@ function getNumericField(value: Record<string, unknown>, key: string): number |
68
62
  }
69
63
 
70
64
  function getErrorStatus(error: unknown): number | null {
71
- if (!isObject(error)) return null
65
+ if (!isRecord(error)) return null
72
66
  return getNumericField(error, 'status') ?? getNumericField(error, 'statusCode')
73
67
  }
74
68
 
@@ -76,44 +70,11 @@ function isRateLimitError(error: unknown): boolean {
76
70
  return getErrorStatus(error) === 429
77
71
  }
78
72
 
79
- function stringifyUnknown(value: unknown, maxChars: number): string | null {
80
- if (value === null || value === undefined) return null
81
- const raw = (() => {
82
- if (typeof value === 'string') return value
83
- if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
84
- return String(value)
85
- }
86
- if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
87
- if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
88
- if (Array.isArray(value)) {
89
- try {
90
- return JSON.stringify(value)
91
- } catch {
92
- return `[array(${value.length})]`
93
- }
94
- }
95
-
96
- try {
97
- return JSON.stringify(value)
98
- } catch {
99
- const maybeName =
100
- isObject(value) && typeof (value as { constructor?: { name?: unknown } }).constructor?.name === 'string'
101
- ? (value as { constructor?: { name?: string } }).constructor?.name
102
- : 'Object'
103
- return `[object ${maybeName}]`
104
- }
105
- })()
106
-
107
- const normalized = compactWhitespace(raw)
108
- if (!normalized) return null
109
- return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}...` : normalized
110
- }
111
-
112
73
  function formatError(tag: string, error: unknown): Error {
113
74
  const status = getErrorStatus(error)
114
75
  const rateLimited = isRateLimitError(error)
115
76
  const message = error instanceof Error ? error.message : String(error)
116
- const errorRecord = isObject(error) ? error : null
77
+ const errorRecord = isRecord(error) ? error : null
117
78
  const responseBody = errorRecord ? stringifyUnknown(errorRecord.responseBody, 600) : null
118
79
  const responseData = errorRecord ? stringifyUnknown(errorRecord.data, 600) : null
119
80
  const requestUrl = errorRecord ? stringifyUnknown(errorRecord.url, 200) : null
@@ -164,59 +125,6 @@ function formatSchemaIssueSummary(issues: Array<{ path: PropertyKey[]; message:
164
125
  .join('; ')
165
126
  }
166
127
 
167
- export function extractJsonObjectCandidates(text: string): string[] {
168
- const trimmed = text.trim()
169
- if (!trimmed) return []
170
-
171
- const candidates: string[] = [trimmed]
172
- const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)
173
- if (fencedMatch?.[1]) {
174
- candidates.push(fencedMatch[1].trim())
175
- }
176
-
177
- let start = -1
178
- let depth = 0
179
- let inString = false
180
- let escaping = false
181
-
182
- for (let index = 0; index < trimmed.length; index += 1) {
183
- const character = trimmed[index]
184
-
185
- if (escaping) {
186
- escaping = false
187
- continue
188
- }
189
-
190
- if (character === '\\') {
191
- escaping = true
192
- continue
193
- }
194
-
195
- if (character === '"') {
196
- inString = !inString
197
- continue
198
- }
199
-
200
- if (inString) continue
201
-
202
- if (character === '{') {
203
- if (depth === 0) start = index
204
- depth += 1
205
- continue
206
- }
207
-
208
- if (character === '}') {
209
- depth -= 1
210
- if (depth === 0 && start >= 0) {
211
- candidates.push(trimmed.slice(start, index + 1))
212
- start = -1
213
- }
214
- }
215
- }
216
-
217
- return [...new Set(candidates.filter((candidate) => candidate.length > 0))]
218
- }
219
-
220
128
  function parseStructuredCandidate<T>(params: {
221
129
  schema: ZodSchema<T>
222
130
  candidate: unknown
@@ -226,7 +134,7 @@ function parseStructuredCandidate<T>(params: {
226
134
  return { data: direct.data, source: 'root' }
227
135
  }
228
136
 
229
- if (isObject(params.candidate)) {
137
+ if (isRecord(params.candidate)) {
230
138
  for (const key of ['output', 'result', 'data'] as const) {
231
139
  const nested = params.candidate[key]
232
140
  const nestedParsed = params.schema.safeParse(nested)
@@ -239,66 +147,6 @@ function parseStructuredCandidate<T>(params: {
239
147
  return null
240
148
  }
241
149
 
242
- function parseStructuredTextFallback<T>(params: { schema: ZodSchema<T>; text: string }): T {
243
- const candidates = extractJsonObjectCandidates(params.text)
244
- if (candidates.length === 0) {
245
- throw new Error('Structured fallback did not contain a JSON object candidate.')
246
- }
247
-
248
- let lastError = 'Structured fallback could not be parsed.'
249
-
250
- for (const candidateText of candidates) {
251
- let parsedJson: unknown
252
-
253
- try {
254
- parsedJson = JSON.parse(candidateText) as unknown
255
- } catch {
256
- lastError = 'Structured fallback JSON parsing failed.'
257
- continue
258
- }
259
-
260
- const parsed = parseStructuredCandidate({ schema: params.schema, candidate: parsedJson })
261
- if (parsed) {
262
- return parsed.data
263
- }
264
-
265
- const issues = params.schema.safeParse(parsedJson)
266
- if (!issues.success) {
267
- lastError = `Structured fallback failed schema validation: ${formatSchemaIssueSummary(issues.error.issues)}`
268
- }
269
- }
270
-
271
- throw new Error(lastError)
272
- }
273
-
274
- function parseStructuredTextWithFallbacks<T>(params: {
275
- schema: ZodSchema<T>
276
- text: string
277
- textFallbackParser?: (text: string) => T | null
278
- }): T {
279
- try {
280
- return parseStructuredTextFallback({ schema: params.schema, text: params.text })
281
- } catch (error) {
282
- const parseError = error instanceof Error ? error : new Error(String(error))
283
-
284
- if (params.textFallbackParser) {
285
- const parsedCandidate = params.textFallbackParser(params.text)
286
- if (parsedCandidate !== null) {
287
- const validated = params.schema.safeParse(parsedCandidate)
288
- if (validated.success) {
289
- return validated.data
290
- }
291
-
292
- throw new Error(
293
- `Custom text fallback failed schema validation: ${formatSchemaIssueSummary(validated.error.issues)}`,
294
- )
295
- }
296
- }
297
-
298
- throw parseError
299
- }
300
- }
301
-
302
150
  export function createHelperModelRuntime() {
303
151
  async function generateHelperText(params: GenerateHelperTextParams): Promise<string> {
304
152
  const systemPrompt = resolveSystemPrompt({
@@ -361,52 +209,16 @@ export function createHelperModelRuntime() {
361
209
  return parsed.data
362
210
  }
363
211
 
364
- if (params.normalizeCandidate && isObject(result.output)) {
365
- const normalized = params.normalizeCandidate(result.output)
366
- const normalizedParsed = parseStructuredCandidate({ schema: params.schema, candidate: normalized })
367
- if (normalizedParsed) {
368
- return normalizedParsed.data
369
- }
370
- }
371
-
372
- if (typeof result.text === 'string' && result.text.trim().length > 0) {
373
- return parseStructuredTextWithFallbacks({
374
- schema: params.schema,
375
- text: result.text,
376
- textFallbackParser: params.textFallbackParser,
377
- })
378
- }
379
-
380
- const fallbackParsed = params.schema.safeParse(result.output)
381
- if (!fallbackParsed.success) {
212
+ const directParsed = params.schema.safeParse(result.output)
213
+ if (!directParsed.success) {
382
214
  throw new Error(
383
- `Structured output failed schema validation: ${formatSchemaIssueSummary(fallbackParsed.error.issues)}`,
215
+ `Structured output failed schema validation: ${formatSchemaIssueSummary(directParsed.error.issues)}`,
384
216
  )
385
217
  }
386
218
 
387
- return fallbackParsed.data
219
+ return directParsed.data
388
220
  } catch (error) {
389
- const structuredError = formatError(params.tag, error)
390
- const fallbackMessages: string[] = []
391
- const fallbackPrompts = [
392
- systemPrompt,
393
- ...(baseSystemPrompt && baseSystemPrompt !== systemPrompt ? [baseSystemPrompt] : []),
394
- ]
395
-
396
- for (const fallbackPrompt of fallbackPrompts) {
397
- try {
398
- const fallbackText = await generateHelperText({ ...params, systemPrompt: fallbackPrompt })
399
- return parseStructuredTextWithFallbacks({
400
- schema: params.schema,
401
- text: fallbackText,
402
- textFallbackParser: params.textFallbackParser,
403
- })
404
- } catch (fallbackError) {
405
- fallbackMessages.push(fallbackError instanceof Error ? fallbackError.message : String(fallbackError))
406
- }
407
- }
408
-
409
- throw new Error(`${structuredError.message}; structured_fallback=${fallbackMessages.join(' | ')}`)
221
+ throw formatError(params.tag, error)
410
222
  }
411
223
  }
412
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'
@@ -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,3 +1,5 @@
1
+ import { z } from 'zod'
2
+
1
3
  import { agentDisplayNames, agentShortDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
2
4
  import { compactWhitespace } from '../utils/string'
3
5
 
@@ -33,11 +35,9 @@ function createLabelPrefixRegex(labelRoles: readonly string[]): RegExp | null {
33
35
  return new RegExp(`^(?:${aliases.map((role) => escapeRegex(role)).join('|')})\\s*:\\s*`, 'i')
34
36
  }
35
37
 
36
- export interface MemoryBlockEntry {
37
- role: string
38
- content: string
39
- timestamp: string
40
- }
38
+ const MemoryBlockEntrySchema = z.object({ role: z.string(), content: z.string(), timestamp: z.string() })
39
+
40
+ export type MemoryBlockEntry = z.infer<typeof MemoryBlockEntrySchema>
41
41
 
42
42
  export interface MemoryBlockRuntime {
43
43
  normalizeMemoryBlockEntry: (entry: string) => string
@@ -184,10 +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
- if (!parsed.every((item: unknown) => typeof item === 'object' && item !== null)) return []
190
- return parsed as MemoryBlockEntry[]
187
+ const parsed = z.array(MemoryBlockEntrySchema).safeParse(JSON.parse(trimmed))
188
+ return parsed.success ? parsed.data : []
191
189
  } catch {
192
190
  return []
193
191
  }
@@ -214,8 +212,8 @@ export function createMemoryBlockRuntime(options: CreateMemoryBlockRuntimeOption
214
212
  .some((line) => {
215
213
  const match = line.match(/^([a-z][a-z0-9_ -]*)\s*:\s*(.+)$/i)
216
214
  if (!match) return false
217
- const rowRole = normalizeMemoryBlockRole(match[1] ?? '')
218
- const rowContent = match[2] ?? ''
215
+ const rowRole = normalizeMemoryBlockRole(match[1])
216
+ const rowContent = match[2]
219
217
  if (rowRole !== roleLower) return false
220
218
  return normalizeMemoryBlockEntry(rowContent) === normalizedTarget
221
219
  })
@@ -1,8 +1,8 @@
1
- import { compactWhitespace } from '../utils/string'
1
+ import { clampImportance, compactWhitespace } from '../utils/string'
2
2
 
3
3
  const SCORE_WEIGHTS = {
4
4
  durability: { core: 0.35, standard: 0.2, weak: 0.05 },
5
- type: { decision: 0.25, preference: 0.18, default: 0.1 },
5
+ type: { decision: 0.25, fact: 0.18, preference: 0.15, default: 0.1 },
6
6
  maxContentLength: 120,
7
7
  } as const
8
8
 
@@ -107,11 +107,6 @@ interface MemoryActionPlan<TRelation extends string = string> {
107
107
  relations: MemoryActionRelation<TRelation>[]
108
108
  }
109
109
 
110
- function clampImportance(value: number): number {
111
- if (!Number.isFinite(value)) return 0
112
- return Math.max(0, Math.min(1, value))
113
- }
114
-
115
110
  function normalizeMemoryKey(text: string): string {
116
111
  return text
117
112
  .toLowerCase()
@@ -135,8 +130,10 @@ function scoreFact<T extends MemoryFactInput>(fact: T): number {
135
130
  type === 'decision'
136
131
  ? SCORE_WEIGHTS.type.decision
137
132
  : type === 'fact'
138
- ? SCORE_WEIGHTS.type.preference
139
- : SCORE_WEIGHTS.type.default
133
+ ? SCORE_WEIGHTS.type.fact
134
+ : type === 'preference'
135
+ ? SCORE_WEIGHTS.type.preference
136
+ : SCORE_WEIGHTS.type.default
140
137
  const lengthWeight =
141
138
  Math.min(fact.content.length, SCORE_WEIGHTS.maxContentLength) / SCORE_WEIGHTS.maxContentLength / 10
142
139
 
@@ -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
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 {