@lota-sdk/core 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/infrastructure/schema/00_identity.surql +0 -2
  2. package/infrastructure/schema/01_memory.surql +1 -1
  3. package/infrastructure/schema/02_execution_plan.surql +62 -1
  4. package/infrastructure/schema/03_learned_skill.surql +1 -1
  5. package/infrastructure/schema/06_playbook.surql +25 -0
  6. package/infrastructure/schema/07_institutional_memory.surql +13 -0
  7. package/infrastructure/schema/08_quality_metrics.surql +17 -0
  8. package/package.json +12 -8
  9. package/src/ai/definitions.ts +81 -3
  10. package/src/ai/embedding-cache.ts +2 -4
  11. package/src/ai/index.ts +0 -2
  12. package/src/bifrost/bifrost.ts +2 -7
  13. package/src/bifrost/cache-headers.ts +8 -0
  14. package/src/bifrost/index.ts +1 -0
  15. package/src/config/agent-defaults.ts +31 -21
  16. package/src/config/agent-types.ts +11 -0
  17. package/src/config/constants.ts +2 -14
  18. package/src/config/debug-logger.ts +5 -1
  19. package/src/config/index.ts +3 -0
  20. package/src/config/model-constants.ts +16 -34
  21. package/src/config/search.ts +1 -15
  22. package/src/create-runtime.ts +269 -178
  23. package/src/db/cursor-pagination.ts +3 -6
  24. package/src/db/index.ts +2 -0
  25. package/src/db/memory-store.helpers.ts +1 -3
  26. package/src/db/memory-store.rows.ts +7 -7
  27. package/src/db/memory-store.ts +14 -18
  28. package/src/db/memory.ts +13 -13
  29. package/src/db/schema-fingerprint.ts +1 -3
  30. package/src/db/service.ts +153 -79
  31. package/src/db/startup.ts +6 -10
  32. package/src/db/surreal-mutation.ts +43 -0
  33. package/src/db/tables.ts +7 -0
  34. package/src/db/workstream-message-row.ts +15 -0
  35. package/src/embeddings/provider.ts +1 -1
  36. package/src/queues/context-compaction.queue.ts +15 -46
  37. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  38. package/src/queues/document-processor.queue.ts +2 -4
  39. package/src/queues/index.ts +3 -0
  40. package/src/queues/memory-consolidation.queue.ts +16 -51
  41. package/src/queues/plan-scheduler.queue.ts +97 -0
  42. package/src/queues/post-chat-memory.queue.ts +20 -55
  43. package/src/queues/queue-factory.ts +100 -0
  44. package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
  45. package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
  46. package/src/queues/skill-extraction.queue.ts +15 -47
  47. package/src/queues/workstream-title-generation.queue.ts +15 -47
  48. package/src/redis/connection.ts +6 -0
  49. package/src/redis/index.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +1 -2
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +109 -35
  53. package/src/runtime/approval-continuation.ts +12 -6
  54. package/src/runtime/context-compaction-runtime.ts +1 -1
  55. package/src/runtime/context-compaction.ts +24 -64
  56. package/src/runtime/execution-plan.ts +22 -18
  57. package/src/runtime/graph-designer.ts +15 -0
  58. package/src/runtime/helper-model.ts +9 -197
  59. package/src/runtime/index.ts +3 -1
  60. package/src/runtime/llm-content.ts +1 -1
  61. package/src/runtime/memory-block.ts +9 -11
  62. package/src/runtime/memory-pipeline.ts +6 -9
  63. package/src/runtime/plugin-resolution.ts +35 -0
  64. package/src/runtime/plugin-types.ts +72 -0
  65. package/src/runtime/retrieval-adapters.ts +1 -1
  66. package/src/runtime/runtime-config.ts +111 -14
  67. package/src/runtime/runtime-extensions.ts +2 -3
  68. package/src/runtime/runtime-worker-registry.ts +6 -0
  69. package/src/runtime/social-chat.ts +752 -0
  70. package/src/runtime/team-consultation-orchestrator.ts +45 -32
  71. package/src/runtime/team-consultation-prompts.ts +11 -2
  72. package/src/runtime/title-helpers.ts +2 -4
  73. package/src/runtime/workstream-chat-helpers.ts +1 -1
  74. package/src/services/adaptive-playbook.service.ts +152 -0
  75. package/src/services/agent-executor.service.ts +292 -0
  76. package/src/services/artifact-provenance.service.ts +172 -0
  77. package/src/services/attachment.service.ts +6 -11
  78. package/src/services/context-compaction.service.ts +72 -55
  79. package/src/services/context-enrichment.service.ts +33 -0
  80. package/src/services/coordination-registry.service.ts +117 -0
  81. package/src/services/document-chunk.service.ts +2 -4
  82. package/src/services/domain-agent-executor.service.ts +71 -0
  83. package/src/services/execution-plan.service.ts +269 -50
  84. package/src/services/feedback-loop.service.ts +96 -0
  85. package/src/services/global-orchestrator.service.ts +148 -0
  86. package/src/services/index.ts +27 -0
  87. package/src/services/institutional-memory.service.ts +145 -0
  88. package/src/services/learned-skill.service.ts +24 -5
  89. package/src/services/memory-assessment.service.ts +3 -2
  90. package/src/services/memory-utils.ts +3 -8
  91. package/src/services/memory.service.ts +49 -61
  92. package/src/services/monitoring-window.service.ts +86 -0
  93. package/src/services/mutating-approval.service.ts +1 -1
  94. package/src/services/node-workspace.service.ts +155 -0
  95. package/src/services/notification.service.ts +39 -0
  96. package/src/services/organization-member.service.ts +11 -4
  97. package/src/services/organization.service.ts +5 -5
  98. package/src/services/ownership-dispatcher.service.ts +403 -0
  99. package/src/services/plan-approval.service.ts +1 -1
  100. package/src/services/plan-builder.service.ts +1 -0
  101. package/src/services/plan-checkpoint.service.ts +30 -2
  102. package/src/services/plan-compiler.service.ts +5 -0
  103. package/src/services/plan-coordination.service.ts +152 -0
  104. package/src/services/plan-cycle.service.ts +284 -0
  105. package/src/services/plan-deadline.service.ts +287 -0
  106. package/src/services/plan-executor.service.ts +384 -40
  107. package/src/services/plan-run.service.ts +41 -7
  108. package/src/services/plan-scheduler.service.ts +240 -0
  109. package/src/services/plan-template.service.ts +117 -0
  110. package/src/services/plan-validator.service.ts +84 -2
  111. package/src/services/plan-workspace.service.ts +83 -0
  112. package/src/services/playbook-registry.service.ts +67 -0
  113. package/src/services/plugin-executor.service.ts +103 -0
  114. package/src/services/quality-metrics.service.ts +132 -0
  115. package/src/services/recent-activity.service.ts +28 -34
  116. package/src/services/skill-resolver.service.ts +19 -0
  117. package/src/services/social-chat-history.service.ts +197 -0
  118. package/src/services/system-executor.service.ts +105 -0
  119. package/src/services/workstream-message.service.ts +13 -37
  120. package/src/services/workstream-plan-registry.service.ts +22 -0
  121. package/src/services/workstream-title.service.ts +3 -1
  122. package/src/services/workstream-turn-preparation.service.ts +34 -89
  123. package/src/services/workstream.service.ts +33 -55
  124. package/src/services/workstream.types.ts +9 -9
  125. package/src/services/write-intent-validator.service.ts +81 -0
  126. package/src/storage/attachment-parser.ts +1 -1
  127. package/src/storage/attachment-utils.ts +1 -1
  128. package/src/storage/generated-document-storage.service.ts +3 -2
  129. package/src/system-agents/context-compaction.agent.ts +2 -0
  130. package/src/system-agents/delegated-agent-factory.ts +5 -0
  131. package/src/system-agents/memory-reranker.agent.ts +4 -2
  132. package/src/system-agents/memory.agent.ts +2 -0
  133. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
  134. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
  135. package/src/system-agents/skill-extractor.agent.ts +2 -0
  136. package/src/system-agents/skill-manager.agent.ts +2 -0
  137. package/src/system-agents/title-generator.agent.ts +2 -0
  138. package/src/tools/execution-plan.tool.ts +17 -23
  139. package/src/tools/index.ts +0 -1
  140. package/src/tools/research-topic.tool.ts +2 -0
  141. package/src/tools/team-think.tool.ts +5 -6
  142. package/src/utils/async.ts +2 -1
  143. package/src/utils/date-time.ts +4 -32
  144. package/src/utils/env.ts +8 -0
  145. package/src/utils/errors.ts +42 -10
  146. package/src/utils/index.ts +9 -0
  147. package/src/utils/string.ts +114 -1
  148. package/src/workers/index.ts +1 -0
  149. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  150. package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
  151. package/src/workers/skill-extraction.runner.ts +26 -6
  152. package/src/workers/utils/file-section-chunker.ts +2 -1
  153. package/src/workers/utils/repo-structure-extractor.ts +2 -2
  154. package/src/workers/utils/repomix-file-sections.ts +2 -2
  155. package/src/workers/utils/sandbox-error.ts +11 -2
  156. package/src/workers/utils/workstream-message-query.ts +14 -25
  157. package/src/workers/worker-utils.ts +2 -2
  158. package/src/runtime/workstream-routing-policy.ts +0 -267
  159. package/src/tools/log-hello-world.tool.ts +0 -17
@@ -27,7 +27,8 @@ async function buildTeamThinkAgentTools(
27
27
  return { tools: result.tools as Record<string, unknown> }
28
28
  }
29
29
 
30
- const TEAM_THINK_AGENT_MAX_RETRIES = 4
30
+ const TEAM_THINK_AGENT_MAX_RETRIES = 1
31
+ const TEAM_THINK_AGENT_MAX_STEPS = 3
31
32
 
32
33
  export function createTeamThinkTool(params: {
33
34
  historyMessages: ChatMessage[]
@@ -39,7 +40,6 @@ export function createTeamThinkTool(params: {
39
40
  availableUploads: ReadableUploadMetadata[]
40
41
  provideRepoTool: boolean
41
42
  defaultRepoSectionsByAgent: Record<string, DefaultRepoSections | undefined>
42
- reasoningProfile: 'fast' | 'standard' | 'deep'
43
43
  systemWorkspaceDetails?: string
44
44
  getPreSeededMemoriesSection: (agentId: string) => Promise<string | undefined>
45
45
  retrievedKnowledgeSection?: string
@@ -58,7 +58,6 @@ export function createTeamThinkTool(params: {
58
58
  mode: 'fixedWorkstreamMode',
59
59
  onboardingActive: false,
60
60
  linearInstalled: false,
61
- reasoningProfile: runParams.reasoningProfile,
62
61
  systemWorkspaceDetails: runParams.systemWorkspaceDetails,
63
62
  preSeededMemoriesSection: runParams.preSeededMemoriesSection,
64
63
  retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
@@ -75,7 +74,7 @@ export function createTeamThinkTool(params: {
75
74
  workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
76
75
  workstreamId: params.workstreamId,
77
76
  githubInstalled: params.githubInstalled,
78
- provideRepoTool: params.provideRepoTool,
77
+ provideRepoTool: agentId !== 'mentor' && params.provideRepoTool,
79
78
  availableUploads: params.availableUploads,
80
79
  defaultRepoSections: params.defaultRepoSectionsByAgent[agentId],
81
80
  context: params.context,
@@ -83,7 +82,8 @@ export function createTeamThinkTool(params: {
83
82
  })
84
83
  const agentConfig = config as Record<string, unknown>
85
84
  const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : agentId
86
- const maxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
85
+ const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
86
+ const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
87
87
  const agent = createAgent[agentId_]({
88
88
  mode: 'fixedWorkstreamMode',
89
89
  tools,
@@ -114,7 +114,6 @@ export function createTeamThinkTool(params: {
114
114
  latestUserMessageId: params.latestUserMessageId,
115
115
  availableUploads: params.availableUploads,
116
116
  defaultRepoSectionsByAgent: params.defaultRepoSectionsByAgent,
117
- reasoningProfile: params.reasoningProfile,
118
117
  systemWorkspaceDetails: params.systemWorkspaceDetails,
119
118
  getPreSeededMemoriesSection: params.getPreSeededMemoriesSection,
120
119
  retrievedKnowledgeSection: params.retrievedKnowledgeSection,
@@ -1,3 +1,4 @@
1
+ import { serverLogger } from '../config/logger'
1
2
  import { getErrorMessage } from './errors'
2
3
 
3
4
  class TimeoutError extends Error {
@@ -41,7 +42,7 @@ export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
41
42
  }
42
43
  }
43
44
 
44
- const _defaultSafeEnqueue = createSafeEnqueue({ warn: console.warn })
45
+ const _defaultSafeEnqueue = createSafeEnqueue({ warn: (message: string) => serverLogger.warn`${message}` })
45
46
  export function safeEnqueue<T>(
46
47
  operation: () => T | Promise<T>,
47
48
  options: { operationName: string; onError?: (error: unknown) => void; logPrefix?: string },
@@ -1,34 +1,6 @@
1
- export function toIsoDateTimeString(value: unknown): string {
2
- if (value instanceof Date) {
3
- return value.toISOString()
4
- }
1
+ export { toIsoDateTimeString, toOptionalIsoDateTimeString } from '@lota-sdk/shared'
5
2
 
6
- // Assume API boundaries already use ISO strings.
7
- if (typeof value === 'string') {
8
- return value
9
- }
10
-
11
- // Support unix timestamps (seconds or milliseconds).
12
- if (typeof value === 'number') {
13
- const millis = value < 1_000_000_000_000 ? value * 1000 : value
14
- return new Date(millis).toISOString()
15
- }
16
-
17
- // Support objects that expose toISOString (e.g., SurrealDB temporal types).
18
- if (value && typeof value === 'object') {
19
- const maybeToIso = (value as { toISOString?: unknown }).toISOString
20
- if (typeof maybeToIso === 'function') {
21
- return (value as { toISOString: () => string }).toISOString()
22
- }
23
- }
24
-
25
- return String(value)
26
- }
27
-
28
- export function toOptionalIsoDateTimeString(value: unknown): string | undefined {
29
- if (value === null || value === undefined || value === '') {
30
- return undefined
31
- }
32
-
33
- return toIsoDateTimeString(value)
3
+ export function toDatabaseDateTime(value: string | Date | null | undefined): Date | undefined {
4
+ if (value === null || value === undefined) return undefined
5
+ return value instanceof Date ? value : new Date(value)
34
6
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Reads a required environment variable. Throws if the variable is missing or empty.
3
+ */
4
+ export function getRequiredEnv(key: string): string {
5
+ const value = process.env[key]
6
+ if (!value) throw new Error(`Missing required env var: ${key}`)
7
+ return value
8
+ }
@@ -1,13 +1,4 @@
1
- export function getErrorMessage(error: unknown): string {
2
- if (error instanceof Error) return error.message
3
- if (typeof error === 'string') return error
4
-
5
- try {
6
- return JSON.stringify(error)
7
- } catch {
8
- return String(error)
9
- }
10
- }
1
+ export { getErrorMessage } from '@lota-sdk/shared'
11
2
 
12
3
  export function toError(value: unknown): Error {
13
4
  return value instanceof Error ? value : new Error(String(value))
@@ -41,3 +32,44 @@ export class BadRequestError extends AppError {
41
32
  super(message, 'BAD_REQUEST', 400)
42
33
  }
43
34
  }
35
+
36
+ type ErrorCode =
37
+ | 'UNAUTHORIZED'
38
+ | 'FORBIDDEN'
39
+ | 'NOT_FOUND'
40
+ | 'CONFLICT'
41
+ | 'BAD_REQUEST'
42
+ | 'VALIDATION_ERROR'
43
+ | 'INTERNAL_SERVER_ERROR'
44
+ | 'HTTP_ERROR'
45
+ | 'TOO_MANY_REQUESTS'
46
+
47
+ interface ValidationIssue {
48
+ path: string
49
+ message: string
50
+ }
51
+
52
+ export interface ErrorBody {
53
+ error: { code: ErrorCode; message: string; issues?: ValidationIssue[] }
54
+ }
55
+
56
+ function createErrorResponse(code: ErrorCode, message: string): ErrorBody {
57
+ return { error: { code, message } }
58
+ }
59
+
60
+ function createValidationErrorResponse(message: string, issues: ValidationIssue[]): ErrorBody {
61
+ return { error: { code: 'VALIDATION_ERROR', message, issues } }
62
+ }
63
+
64
+ export const errorResponses = {
65
+ unauthorized: (message = 'Invalid credentials') => createErrorResponse('UNAUTHORIZED', message),
66
+ forbidden: (message = 'Access denied') => createErrorResponse('FORBIDDEN', message),
67
+ notFound: (message: string) => createErrorResponse('NOT_FOUND', message),
68
+ conflict: (message: string) => createErrorResponse('CONFLICT', message),
69
+ badRequest: (message: string) => createErrorResponse('BAD_REQUEST', message),
70
+ tooManyRequests: (message = 'Too many requests') => createErrorResponse('TOO_MANY_REQUESTS', message),
71
+ serverError: (message = 'Internal server error') => createErrorResponse('INTERNAL_SERVER_ERROR', message),
72
+ validationError: (issues: ValidationIssue[], message = 'Validation failed') =>
73
+ createValidationErrorResponse(message, issues),
74
+ httpError: (message: string) => createErrorResponse('HTTP_ERROR', message),
75
+ } as const
@@ -1,14 +1,23 @@
1
1
  export * from './async'
2
2
  export * from './date-time'
3
+ export * from './env'
3
4
  export * from './errors'
4
5
  export * from './hono-error-handler'
5
6
  export * from './sse-keepalive'
6
7
  export {
7
8
  CHARS_PER_TOKEN_ESTIMATE,
9
+ clampImportance,
10
+ compactRecord,
8
11
  compactWhitespace,
9
12
  isRecord,
13
+ parseLineList,
14
+ readRecordArray,
10
15
  readString,
16
+ readStringArray,
11
17
  readStringField,
18
+ slugify,
19
+ stringifyLineList,
20
+ stringifyUnknown,
12
21
  truncateOptionalText,
13
22
  truncateText,
14
23
  } from './string'
@@ -29,7 +29,7 @@ export function readStringField(record: Record<string, unknown>, key: string): s
29
29
  * Returns the original string when it fits within maxChars.
30
30
  */
31
31
  export function truncateText(value: string, maxChars: number): string {
32
- return value.length <= maxChars ? value : `${value.slice(0, maxChars).trimEnd()}...`
32
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars - 3).trimEnd()}...`
33
33
  }
34
34
 
35
35
  /**
@@ -59,7 +59,120 @@ export function readRecord(value: unknown): Record<string, unknown> | null {
59
59
  return value as Record<string, unknown>
60
60
  }
61
61
 
62
+ /**
63
+ * Strips null and undefined values from a record, returning a new object with only defined entries.
64
+ */
65
+ export function compactRecord(value: Record<string, unknown>): Record<string, unknown> {
66
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== null && entry !== undefined))
67
+ }
68
+
69
+ /**
70
+ * Converts a string into a URL-safe slug: lowercase, non-alphanumeric runs
71
+ * replaced with hyphens, leading/trailing hyphens stripped, capped at 80 chars.
72
+ */
73
+ export function slugify(value: string): string {
74
+ return value
75
+ .trim()
76
+ .toLowerCase()
77
+ .replace(/[^a-z0-9]+/g, '-')
78
+ .replace(/(^-|-$)/g, '')
79
+ .slice(0, 80)
80
+ }
81
+
62
82
  /**
63
83
  * Rough character-to-token estimate used for context budget calculations.
64
84
  */
65
85
  export const CHARS_PER_TOKEN_ESTIMATE = 3
86
+
87
+ /**
88
+ * Converts an unknown value to a trimmed, non-empty string representation.
89
+ * Returns null for null, undefined, empty, or whitespace-only results.
90
+ *
91
+ * When maxChars is provided, the result is compacted and truncated.
92
+ * Without maxChars, strings are trimmed and objects are JSON-serialized.
93
+ */
94
+ export function stringifyUnknown(value: unknown, maxChars?: number): string | null {
95
+ if (value === null || value === undefined) return null
96
+
97
+ const raw = (() => {
98
+ if (typeof value === 'string') return value
99
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
100
+ return String(value)
101
+ }
102
+ if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
103
+ if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
104
+ if (Array.isArray(value)) {
105
+ try {
106
+ return JSON.stringify(value)
107
+ } catch {
108
+ return `[array(${value.length})]`
109
+ }
110
+ }
111
+
112
+ try {
113
+ return JSON.stringify(value)
114
+ } catch {
115
+ const maybeName =
116
+ isRecord(value) && typeof (value as { constructor?: { name?: unknown } }).constructor?.name === 'string'
117
+ ? (value as { constructor?: { name?: string } }).constructor?.name
118
+ : 'Object'
119
+ return `[object ${maybeName}]`
120
+ }
121
+ })()
122
+
123
+ if (maxChars !== undefined) {
124
+ const normalized = compactWhitespace(raw)
125
+ if (!normalized) return null
126
+ return truncateText(normalized, maxChars)
127
+ }
128
+
129
+ const trimmed = raw.trim()
130
+ return trimmed.length > 0 ? trimmed : null
131
+ }
132
+
133
+ /**
134
+ * Clamps a numeric importance/confidence value to [0, 1].
135
+ * Returns 0 for non-finite values.
136
+ */
137
+ export function clampImportance(value: number): number {
138
+ if (!Number.isFinite(value)) return 0
139
+ return Math.max(0, Math.min(1, value))
140
+ }
141
+
142
+ /**
143
+ * Splits a string on newlines, trims each line, and filters empties.
144
+ */
145
+ export function parseLineList(value: string): string[] {
146
+ return value
147
+ .split('\n')
148
+ .map((entry) => entry.trim())
149
+ .filter((entry) => entry.length > 0)
150
+ }
151
+
152
+ /**
153
+ * Joins an array of values into a newline-separated string.
154
+ * Returns an empty string for non-array inputs.
155
+ */
156
+ export function stringifyLineList(value: unknown): string {
157
+ return Array.isArray(value) ? value.map(String).join('\n') : ''
158
+ }
159
+
160
+ /**
161
+ * Reads an array of strings from an unknown value.
162
+ * Returns an empty array when the input is not an array.
163
+ */
164
+ export function readStringArray(value: unknown): string[] {
165
+ if (!Array.isArray(value)) return []
166
+ return value.filter((entry): entry is string => typeof entry === 'string')
167
+ }
168
+
169
+ /**
170
+ * Reads an array of plain records from an unknown value.
171
+ * Returns an empty array when the input is not an array.
172
+ */
173
+ export function readRecordArray(value: unknown): Array<Record<string, unknown>> {
174
+ if (!Array.isArray(value)) return []
175
+ return value.filter(
176
+ (entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null && !Array.isArray(entry),
177
+ )
178
+ }
@@ -5,3 +5,4 @@ export * from './utils/file-section-chunker'
5
5
  export * from './utils/repomix-file-sections'
6
6
  export * from './utils/repo-structure-extractor'
7
7
  export * from './utils/repomix-process-concurrency'
8
+ export * from './utils/sandbox-error'
@@ -2,7 +2,7 @@ import { isAgentName } from '../config/agent-defaults'
2
2
  import { compactWhitespace } from '../utils/string'
3
3
 
4
4
  interface DigestMessageForTranscript {
5
- source: 'workstream'
5
+ source: 'workstream' | 'social'
6
6
  sourceId: string
7
7
  role: 'system' | 'user' | 'assistant'
8
8
  parts: Array<Record<string, unknown>>
@@ -14,6 +14,7 @@ import type { RegularChatMemoryDigestJob } from '../queues/regular-chat-memory-d
14
14
  import { createHelperModelRuntime } from '../runtime/helper-model'
15
15
  import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
16
16
  import { memoryService } from '../services/memory.service'
17
+ import { socialChatHistoryService } from '../services/social-chat-history.service'
17
18
  import { createRegularChatMemoryDigestAgent } from '../system-agents/regular-chat-memory-digest.agent'
18
19
  import { compactWhitespace } from '../utils/string'
19
20
  import { buildDigestTranscript, resolveWorkspaceBootstrapCutoff } from './regular-chat-memory-digest.helpers'
@@ -48,6 +49,7 @@ const helperModelRuntime = createHelperModelRuntime()
48
49
  interface RegularChatDigestRunResult {
49
50
  skipped: boolean
50
51
  processedWorkstreamMessages: number
52
+ processedSocialMessages: number
51
53
  followUpScheduled: boolean
52
54
  }
53
55
 
@@ -130,7 +132,7 @@ async function hasNewEligibleWorkstreamMessages(params: {
130
132
  }
131
133
 
132
134
  async function loadExistingOrganizationMemories(orgId: string): Promise<Array<{ content: string }>> {
133
- return await databaseService.queryMany(
135
+ return databaseService.queryMany(
134
136
  new BoundQuery(
135
137
  `SELECT content, createdAt, id FROM ${TABLES.MEMORY}
136
138
  WHERE metadata.orgId = $orgId
@@ -152,24 +154,24 @@ export async function runRegularChatMemoryDigest(
152
154
  const workspaceProvider = getRuntimeAdapters().services?.workspaceProvider
153
155
  if (!workspaceProvider) {
154
156
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider is not configured`
155
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
157
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
156
158
  }
157
159
 
158
- return await withConfiguredWorkspaceMemoryLock(orgId, async () => {
160
+ return withConfiguredWorkspaceMemoryLock(orgId, async () => {
159
161
  if (
160
162
  !workspaceProvider.getBackgroundCursor ||
161
163
  !workspaceProvider.setBackgroundCursor ||
162
164
  !workspaceProvider.applyProfileProjection
163
165
  ) {
164
166
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: workspaceProvider background/profile methods are incomplete`
165
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
167
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
166
168
  }
167
169
 
168
170
  const workspace = await workspaceProvider.getWorkspace(orgRef)
169
171
  const lifecycleState = await workspaceProvider.getLifecycleState?.(workspace)
170
172
  if (lifecycleState?.bootstrapActive ?? false) {
171
173
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: onboarding is not completed`
172
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
174
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
173
175
  }
174
176
  const projectionState = await workspaceProvider.readProfileProjectionState?.(workspace)
175
177
 
@@ -185,13 +187,23 @@ export async function runRegularChatMemoryDigest(
185
187
  cursor: existingWorkstreamCursor,
186
188
  onboardingCutoff: workstreamOnboardingCutoff,
187
189
  })
190
+ const existingSocialCursor = await socialChatHistoryService.getBackgroundCursor('regular-chat-digest', orgId)
191
+ const socialOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
192
+ hasExistingCursor: existingSocialCursor !== null,
193
+ bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
194
+ })
195
+ const socialMessages = await socialChatHistoryService.listWorkspaceMessages({
196
+ workspaceId: orgId,
197
+ cursor: existingSocialCursor,
198
+ onboardingCutoff: socialOnboardingCutoff,
199
+ })
188
200
 
189
- if (workstreamMessages.length === 0) {
201
+ if (workstreamMessages.length === 0 && socialMessages.length === 0) {
190
202
  serverLogger.info`Skipping regular chat memory digest for ${orgId}: no eligible messages`
191
- return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
203
+ return { skipped: true, processedWorkstreamMessages: 0, processedSocialMessages: 0, followUpScheduled: false }
192
204
  }
193
205
 
194
- const combinedMessages = [...workstreamMessages].sort(compareDigestMessageOrder)
206
+ const combinedMessages = [...workstreamMessages, ...socialMessages].sort(compareDigestMessageOrder)
195
207
  const { transcript, involvedAgentNames } = buildDigestTranscript({ messages: combinedMessages })
196
208
  const existingMemories = await loadExistingOrganizationMemories(orgId)
197
209
 
@@ -219,7 +231,8 @@ export async function runRegularChatMemoryDigest(
219
231
  throw new Error('Regular chat memory digest returned an empty summaryBlock')
220
232
  }
221
233
 
222
- const processedWorkstreamCursor = getLastCursor(combinedMessages)
234
+ const processedWorkstreamCursor = getLastCursor(workstreamMessages)
235
+ const processedSocialCursor = getLastCursor(socialMessages)
223
236
  const digestRunAt = new Date().toISOString()
224
237
 
225
238
  if (synthesis.facts.length > 0) {
@@ -235,6 +248,12 @@ export async function runRegularChatMemoryDigest(
235
248
  digestWorkstreamCursorId: processedWorkstreamCursor.id,
236
249
  }
237
250
  : {}),
251
+ ...(processedSocialCursor
252
+ ? {
253
+ digestSocialCursorCreatedAt: processedSocialCursor.createdAt.toISOString(),
254
+ digestSocialCursorId: processedSocialCursor.id,
255
+ }
256
+ : {}),
238
257
  },
239
258
  agentNames: involvedAgentNames,
240
259
  acquireLock: false,
@@ -248,6 +267,9 @@ export async function runRegularChatMemoryDigest(
248
267
  if (processedWorkstreamCursor) {
249
268
  await workspaceProvider.setBackgroundCursor('regular-chat-digest', orgRef, processedWorkstreamCursor)
250
269
  }
270
+ if (processedSocialCursor) {
271
+ await socialChatHistoryService.setBackgroundCursor('regular-chat-digest', orgId, processedSocialCursor)
272
+ }
251
273
 
252
274
  const workstreamBoundaryCursor = processedWorkstreamCursor ?? existingWorkstreamCursor
253
275
  const hasMoreWorkstreamMessages = await hasNewEligibleWorkstreamMessages({
@@ -255,15 +277,26 @@ export async function runRegularChatMemoryDigest(
255
277
  cursor: workstreamBoundaryCursor,
256
278
  onboardingCutoff: workstreamBoundaryCursor ? null : workstreamOnboardingCutoff,
257
279
  })
280
+ const socialBoundaryCursor = processedSocialCursor ?? existingSocialCursor
281
+ const hasMoreSocialMessages = await socialChatHistoryService.hasWorkspaceMessages({
282
+ workspaceId: orgId,
283
+ cursor: socialBoundaryCursor,
284
+ onboardingCutoff: socialBoundaryCursor ? null : socialOnboardingCutoff,
285
+ })
258
286
 
259
- const followUpScheduled = hasMoreWorkstreamMessages
287
+ const followUpScheduled = hasMoreWorkstreamMessages || hasMoreSocialMessages
260
288
  if (followUpScheduled) {
261
289
  await clearRegularChatMemoryDigestDeduplicationKey(orgId)
262
290
  await enqueueRegularChatMemoryDigest({ orgId })
263
291
  }
264
292
 
265
- serverLogger.info`Regular chat memory digest completed for ${orgId}: workstreamMessages=${workstreamMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
293
+ serverLogger.info`Regular chat memory digest completed for ${orgId}: workstreamMessages=${workstreamMessages.length}, socialMessages=${socialMessages.length}, facts=${synthesis.facts.length}, followUpScheduled=${followUpScheduled}`
266
294
 
267
- return { skipped: false, processedWorkstreamMessages: workstreamMessages.length, followUpScheduled }
295
+ return {
296
+ skipped: false,
297
+ processedWorkstreamMessages: workstreamMessages.length,
298
+ processedSocialMessages: socialMessages.length,
299
+ followUpScheduled,
300
+ }
268
301
  })
269
302
  }
@@ -6,6 +6,7 @@ import type { SkillExtractionJob } from '../queues/skill-extraction.queue'
6
6
  import { createHelperModelRuntime } from '../runtime/helper-model'
7
7
  import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
8
8
  import { learnedSkillService } from '../services/learned-skill.service'
9
+ import { socialChatHistoryService } from '../services/social-chat-history.service'
9
10
  import { createSkillExtractorAgent, SkillExtractionOutputSchema } from '../system-agents/skill-extractor.agent'
10
11
  import type { SkillCandidate } from '../system-agents/skill-extractor.agent'
11
12
  import { createSkillManagerAgent, SkillManagerOutputSchema } from '../system-agents/skill-manager.agent'
@@ -86,7 +87,7 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
86
87
  return { skipped: true, processedMessages: 0, extractedSkills: 0 }
87
88
  }
88
89
 
89
- return await withConfiguredWorkspaceMemoryLock(orgId, async () => {
90
+ return withConfiguredWorkspaceMemoryLock(orgId, async () => {
90
91
  const workspace = await cursorAwareWorkspaceProvider.getWorkspace(orgRef)
91
92
  const lifecycleState = await cursorAwareWorkspaceProvider.getLifecycleState?.(workspace)
92
93
  if (lifecycleState?.bootstrapActive ?? false) {
@@ -100,9 +101,24 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
100
101
  hasExistingCursor: existingCursor !== null,
101
102
  bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
102
103
  })
104
+ const existingSocialCursor = await socialChatHistoryService.getBackgroundCursor('skill-extraction', orgId)
105
+ const socialOnboardingCutoff = resolveWorkspaceBootstrapCutoff({
106
+ hasExistingCursor: existingSocialCursor !== null,
107
+ bootstrapCompletedAt: lifecycleState?.bootstrapCompletedAt,
108
+ })
103
109
 
104
110
  const workstreamIds = await listWorkstreamIdsForOrg(orgRef)
105
- const messages = await listEligibleWorkstreamMessages({ workstreamIds, cursor: existingCursor, onboardingCutoff })
111
+ const workstreamMessages = await listEligibleWorkstreamMessages({
112
+ workstreamIds,
113
+ cursor: existingCursor,
114
+ onboardingCutoff,
115
+ })
116
+ const socialMessages = await socialChatHistoryService.listWorkspaceMessages({
117
+ workspaceId: orgId,
118
+ cursor: existingSocialCursor,
119
+ onboardingCutoff: socialOnboardingCutoff,
120
+ })
121
+ const messages = [...workstreamMessages, ...socialMessages]
106
122
 
107
123
  if (messages.length < MIN_MESSAGE_THRESHOLD) {
108
124
  serverLogger.info`Skipping skill extraction for ${orgId}: only ${messages.length} messages (threshold: ${MIN_MESSAGE_THRESHOLD})`
@@ -225,12 +241,16 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
225
241
  }
226
242
  }
227
243
 
228
- const lastMessage = sortedMessages.at(-1)
229
- if (lastMessage) {
230
- await cursorAwareWorkspaceProvider.setBackgroundCursor('skill-extraction', orgRef, lastMessage.cursor)
244
+ const lastWorkstreamMessage = workstreamMessages.at(-1)
245
+ const lastSocialMessage = socialMessages.at(-1)
246
+ if (lastWorkstreamMessage) {
247
+ await cursorAwareWorkspaceProvider.setBackgroundCursor('skill-extraction', orgRef, lastWorkstreamMessage.cursor)
248
+ }
249
+ if (lastSocialMessage) {
250
+ await socialChatHistoryService.setBackgroundCursor('skill-extraction', orgId, lastSocialMessage.cursor)
231
251
  }
232
252
 
233
- serverLogger.info`Skill extraction completed for ${orgId}: messages=${messages.length}, extracted=${extractedSkills}`
253
+ serverLogger.info`Skill extraction completed for ${orgId}: messages=${messages.length}, socialMessages=${socialMessages.length}, extracted=${extractedSkills}`
234
254
 
235
255
  return { skipped: false, processedMessages: messages.length, extractedSkills }
236
256
  })
@@ -1,8 +1,9 @@
1
+ import { CHARS_PER_TOKEN_ESTIMATE } from '../../utils/string'
2
+
1
3
  export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 250_000
2
4
  export const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
3
5
  export const DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS = 10_000
4
6
  const SECTION_SEPARATOR_LENGTH = 2
5
- const CHARS_PER_TOKEN_ESTIMATE = 3
6
7
  const MIN_CHUNK_CHARS_FLOOR = 512
7
8
 
8
9
  export interface FileSection {
@@ -1,4 +1,4 @@
1
- import { readdir, readFile } from 'node:fs/promises'
1
+ import { readdir } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
4
  import { RepositoryStructureArtifactSchema, RepositoryStructureSummarySchema } from '@lota-sdk/shared'
@@ -119,7 +119,7 @@ async function collectRelativeFilePaths(rootDir: string, currentDir = ''): Promi
119
119
 
120
120
  async function readPackageJson(rootDir: string, relativePath: string): Promise<PackageJson | null> {
121
121
  try {
122
- const raw = await readFile(path.join(rootDir, relativePath), 'utf8')
122
+ const raw = await Bun.file(path.join(rootDir, relativePath)).text()
123
123
  return JSON.parse(raw) as PackageJson
124
124
  } catch {
125
125
  return null
@@ -27,7 +27,7 @@ export function parseRepomixFileSections(repomixOutput: string): FileSection[] {
27
27
  const nextStart = matches[index + 1]?.index ?? source.length
28
28
  const content = source.slice(start, nextStart).trim()
29
29
  if (!content) continue
30
- const filePath = (match[1] ?? '').trim()
30
+ const filePath = match[1].trim()
31
31
  sections.push({ kind: 'file', content, filePath: filePath || undefined })
32
32
  }
33
33
 
@@ -38,5 +38,5 @@ export async function chunkRepomixFileSections(
38
38
  repomixOutput: string,
39
39
  options: FileSectionChunkOptions = {},
40
40
  ): Promise<FileSectionChunk[]> {
41
- return await chunkFileSections(parseRepomixFileSections(repomixOutput), options)
41
+ return chunkFileSections(parseRepomixFileSections(repomixOutput), options)
42
42
  }
@@ -1,5 +1,14 @@
1
- export function toSandboxedWorkerError(error: unknown, context?: string): Error {
2
- const base = error instanceof Error ? error : new Error(String(error))
1
+ export interface SandboxedWorkerError {
2
+ name: string
3
+ message: string
4
+ stack?: string
5
+ }
6
+
7
+ export function toSandboxedWorkerError(error: unknown, context?: string): SandboxedWorkerError {
8
+ const base =
9
+ error instanceof Error
10
+ ? { name: error.name || 'Error', message: error.message, stack: error.stack }
11
+ : { name: 'Error', message: String(error) }
3
12
  if (context) base.message = `${context}: ${base.message}`
4
13
  return base
5
14
  }