@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.
- package/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +12 -8
- package/src/ai/definitions.ts +81 -3
- package/src/ai/embedding-cache.ts +2 -4
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/bifrost/cache-headers.ts +8 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +31 -21
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +269 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.helpers.ts +1 -3
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/schema-fingerprint.ts +1 -3
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +2 -4
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +20 -55
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -2
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +109 -35
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +24 -64
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +3 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +111 -14
- package/src/runtime/runtime-extensions.ts +2 -3
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/social-chat.ts +752 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -32
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +292 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +2 -4
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +27 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +49 -61
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +11 -4
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +384 -40
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +84 -2
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity.service.ts +28 -34
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/social-chat-history.service.ts +197 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +13 -37
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -89
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/context-compaction.agent.ts +2 -0
- package/src/system-agents/delegated-agent-factory.ts +5 -0
- package/src/system-agents/memory-reranker.agent.ts +4 -2
- package/src/system-agents/memory.agent.ts +2 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
- package/src/system-agents/skill-extractor.agent.ts +2 -0
- package/src/system-agents/skill-manager.agent.ts +2 -0
- package/src/system-agents/title-generator.agent.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/research-topic.tool.ts +2 -0
- package/src/tools/team-think.tool.ts +5 -6
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
- package/src/workers/skill-extraction.runner.ts +26 -6
- package/src/workers/utils/file-section-chunker.ts +2 -1
- package/src/workers/utils/repo-structure-extractor.ts +2 -2
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +14 -25
- package/src/workers/worker-utils.ts +2 -2
- package/src/runtime/workstream-routing-policy.ts +0 -267
- 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 =
|
|
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
|
|
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,
|
package/src/utils/async.ts
CHANGED
|
@@ -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:
|
|
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 },
|
package/src/utils/date-time.ts
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
if (value instanceof Date) {
|
|
3
|
-
return value.toISOString()
|
|
4
|
-
}
|
|
1
|
+
export { toIsoDateTimeString, toOptionalIsoDateTimeString } from '@lota-sdk/shared'
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
if (
|
|
8
|
-
|
|
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
|
}
|
package/src/utils/env.ts
ADDED
|
@@ -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
|
+
}
|
package/src/utils/errors.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
export
|
|
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
|
package/src/utils/index.ts
CHANGED
|
@@ -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'
|
package/src/utils/string.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/workers/index.ts
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
41
|
+
return chunkFileSections(parseRepomixFileSections(repomixOutput), options)
|
|
42
42
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
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
|
}
|