@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.
- 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 +8 -7
- package/src/ai/definitions.ts +80 -2
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- 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 +244 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- 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/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/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 +15 -56
- 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/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +22 -60
- 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 +2 -0
- 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 +25 -12
- package/src/runtime/runtime-extensions.ts +2 -2
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- 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 +293 -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 +1 -1
- 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 +26 -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 +42 -59
- 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 +27 -31
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +12 -34
- 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 -66
- 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/delegated-agent-factory.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/team-think.tool.ts +6 -4
- 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.runner.ts +2 -2
- package/src/workers/skill-extraction.runner.ts +1 -1
- package/src/workers/utils/file-section-chunker.ts +2 -1
- 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 +11 -20
- package/src/workers/worker-utils.ts +2 -2
- package/src/tools/log-hello-world.tool.ts +0 -17
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { S3Client } from 'bun'
|
|
2
2
|
|
|
3
3
|
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
4
|
+
import { toSafeSegment } from './attachment-utils'
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
+
function toSafePathSegment(value: string): string {
|
|
6
7
|
return value
|
|
7
8
|
.trim()
|
|
8
9
|
.replace(/\\/g, '/')
|
|
@@ -18,7 +19,7 @@ function buildGeneratedDocumentStorageKey(params: {
|
|
|
18
19
|
}): string {
|
|
19
20
|
const safeOrganizationId = toSafeSegment(params.organizationId)
|
|
20
21
|
const safeNamespace = toSafeSegment(params.namespace)
|
|
21
|
-
const safeRelativePath =
|
|
22
|
+
const safeRelativePath = toSafePathSegment(params.relativePath)
|
|
22
23
|
return `${safeOrganizationId}/generated/${safeNamespace}/${safeRelativePath}`
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -3,6 +3,7 @@ import type { ModelMessage, LanguageModel, ToolLoopAgentSettings, ToolSet } from
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
5
|
import type { ToolDefinition } from '../ai/definitions'
|
|
6
|
+
import { aiLogger } from '../config/logger'
|
|
6
7
|
import { isRecord } from '../utils/string'
|
|
7
8
|
import { assertSubstantiveAgentResult } from './agent-result'
|
|
8
9
|
|
|
@@ -136,6 +137,7 @@ async function generateSubstantiveDelegatedAgentResult(params: {
|
|
|
136
137
|
try {
|
|
137
138
|
return assertSubstantiveAgentResult(result.text, params.label)
|
|
138
139
|
} catch (error) {
|
|
140
|
+
aiLogger.error`Delegated agent returned non-substantive result (label=${params.label}, attempt=${attempt + 1}, textLength=${result.text.length}, textPreview=${result.text.slice(0, 200)})`
|
|
139
141
|
lastError = error
|
|
140
142
|
if (params.abortSignal?.aborted) {
|
|
141
143
|
throw error
|
|
@@ -1,35 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CreateExecutionPlanArgsSchema,
|
|
3
3
|
GetActiveExecutionPlanArgsSchema,
|
|
4
|
+
ListExecutionPlansArgsSchema,
|
|
4
5
|
ResumeExecutionPlanRunArgsSchema,
|
|
5
6
|
ReplaceExecutionPlanArgsSchema,
|
|
6
7
|
SubmitExecutionNodeResultArgsSchema,
|
|
8
|
+
getLatestExecutionPlanResult,
|
|
7
9
|
} from '@lota-sdk/shared'
|
|
8
|
-
import type { ExecutionPlanToolResultData } from '@lota-sdk/shared'
|
|
9
10
|
import { tool } from 'ai'
|
|
10
11
|
|
|
11
12
|
import type { RecordIdRef } from '../db/record-id'
|
|
12
13
|
import { executionPlanService } from '../services/execution-plan.service'
|
|
13
14
|
|
|
14
|
-
function isExecutionPlanResult(value: unknown): value is ExecutionPlanToolResultData {
|
|
15
|
-
return value !== null && value !== undefined && typeof value === 'object' && 'hasPlan' in value
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getLatestExecutionPlanToolResult(output: unknown): ExecutionPlanToolResultData | undefined {
|
|
19
|
-
if (isExecutionPlanResult(output)) {
|
|
20
|
-
return output
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(output)) {
|
|
24
|
-
for (let index = output.length - 1; index >= 0; index -= 1) {
|
|
25
|
-
const candidate = getLatestExecutionPlanToolResult(output[index])
|
|
26
|
-
if (candidate) return candidate
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return undefined
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
export function createCreateExecutionPlanTool(params: {
|
|
34
16
|
orgId: RecordIdRef
|
|
35
17
|
workstreamId: RecordIdRef
|
|
@@ -45,6 +27,7 @@ export function createCreateExecutionPlanTool(params: {
|
|
|
45
27
|
organizationId: params.orgId,
|
|
46
28
|
workstreamId: params.workstreamId,
|
|
47
29
|
leadAgentId: params.agentId,
|
|
30
|
+
dispatchMode: 'deferred',
|
|
48
31
|
input,
|
|
49
32
|
})
|
|
50
33
|
params.onPlanChanged?.()
|
|
@@ -68,6 +51,7 @@ export function createReplaceExecutionPlanTool(params: {
|
|
|
68
51
|
organizationId: params.orgId,
|
|
69
52
|
workstreamId: params.workstreamId,
|
|
70
53
|
leadAgentId: params.agentId,
|
|
54
|
+
dispatchMode: 'deferred',
|
|
71
55
|
input,
|
|
72
56
|
})
|
|
73
57
|
params.onPlanChanged?.()
|
|
@@ -95,21 +79,31 @@ export function createSubmitExecutionNodeResultTool(params: {
|
|
|
95
79
|
return result
|
|
96
80
|
},
|
|
97
81
|
toModelOutput: ({ output }) => {
|
|
98
|
-
const result =
|
|
82
|
+
const result = getLatestExecutionPlanResult(output)
|
|
99
83
|
const summary = result?.message?.trim()
|
|
100
84
|
return { type: 'text', value: summary && summary.length > 0 ? summary : 'Execution node result submitted.' }
|
|
101
85
|
},
|
|
102
86
|
})
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
export function
|
|
89
|
+
export function createListExecutionPlansTool(params: { workstreamId: RecordIdRef }) {
|
|
90
|
+
return tool({
|
|
91
|
+
description:
|
|
92
|
+
'List all active execution plans for this workstream with summary info (title, status, objective, node counts). Use getExecutionPlanDetails to inspect a specific plan.',
|
|
93
|
+
inputSchema: ListExecutionPlansArgsSchema,
|
|
94
|
+
execute: async () => await executionPlanService.listActivePlanSummaries(params.workstreamId),
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createGetExecutionPlanDetailsTool(params: { workstreamId: RecordIdRef }) {
|
|
106
99
|
return tool({
|
|
107
100
|
description:
|
|
108
|
-
'Load
|
|
101
|
+
'Load a specific execution run by runId, or the most recent active run if runId is omitted. Returns full graph state, node contracts, events, approvals, artifacts, and checkpoints.',
|
|
109
102
|
inputSchema: GetActiveExecutionPlanArgsSchema,
|
|
110
103
|
execute: async (input) =>
|
|
111
104
|
await executionPlanService.getActivePlanToolResult({
|
|
112
105
|
workstreamId: params.workstreamId,
|
|
106
|
+
runId: input.runId,
|
|
113
107
|
includeEvents: input.includeEvents,
|
|
114
108
|
includeArtifacts: input.includeArtifacts,
|
|
115
109
|
includeApprovals: input.includeApprovals,
|
package/src/tools/index.ts
CHANGED
|
@@ -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[]
|
|
@@ -58,7 +59,7 @@ export function createTeamThinkTool(params: {
|
|
|
58
59
|
mode: 'fixedWorkstreamMode',
|
|
59
60
|
onboardingActive: false,
|
|
60
61
|
linearInstalled: false,
|
|
61
|
-
reasoningProfile:
|
|
62
|
+
reasoningProfile: 'fast',
|
|
62
63
|
systemWorkspaceDetails: runParams.systemWorkspaceDetails,
|
|
63
64
|
preSeededMemoriesSection: runParams.preSeededMemoriesSection,
|
|
64
65
|
retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
|
|
@@ -75,7 +76,7 @@ export function createTeamThinkTool(params: {
|
|
|
75
76
|
workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
|
|
76
77
|
workstreamId: params.workstreamId,
|
|
77
78
|
githubInstalled: params.githubInstalled,
|
|
78
|
-
provideRepoTool: params.provideRepoTool,
|
|
79
|
+
provideRepoTool: agentId !== 'mentor' && params.provideRepoTool,
|
|
79
80
|
availableUploads: params.availableUploads,
|
|
80
81
|
defaultRepoSections: params.defaultRepoSectionsByAgent[agentId],
|
|
81
82
|
context: params.context,
|
|
@@ -83,7 +84,8 @@ export function createTeamThinkTool(params: {
|
|
|
83
84
|
})
|
|
84
85
|
const agentConfig = config as Record<string, unknown>
|
|
85
86
|
const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : agentId
|
|
86
|
-
const
|
|
87
|
+
const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
|
|
88
|
+
const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
|
|
87
89
|
const agent = createAgent[agentId_]({
|
|
88
90
|
mode: 'fixedWorkstreamMode',
|
|
89
91
|
tools,
|
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
|
@@ -130,7 +130,7 @@ async function hasNewEligibleWorkstreamMessages(params: {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
async function loadExistingOrganizationMemories(orgId: string): Promise<Array<{ content: string }>> {
|
|
133
|
-
return
|
|
133
|
+
return databaseService.queryMany(
|
|
134
134
|
new BoundQuery(
|
|
135
135
|
`SELECT content, createdAt, id FROM ${TABLES.MEMORY}
|
|
136
136
|
WHERE metadata.orgId = $orgId
|
|
@@ -155,7 +155,7 @@ export async function runRegularChatMemoryDigest(
|
|
|
155
155
|
return { skipped: true, processedWorkstreamMessages: 0, followUpScheduled: false }
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
return
|
|
158
|
+
return withConfiguredWorkspaceMemoryLock(orgId, async () => {
|
|
159
159
|
if (
|
|
160
160
|
!workspaceProvider.getBackgroundCursor ||
|
|
161
161
|
!workspaceProvider.setBackgroundCursor ||
|
|
@@ -86,7 +86,7 @@ export async function runSkillExtraction(data: SkillExtractionJob): Promise<Skil
|
|
|
86
86
|
return { skipped: true, processedMessages: 0, extractedSkills: 0 }
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
return
|
|
89
|
+
return withConfiguredWorkspaceMemoryLock(orgId, async () => {
|
|
90
90
|
const workspace = await cursorAwareWorkspaceProvider.getWorkspace(orgRef)
|
|
91
91
|
const lifecycleState = await cursorAwareWorkspaceProvider.getLifecycleState?.(workspace)
|
|
92
92
|
if (lifecycleState?.bootstrapActive ?? false) {
|
|
@@ -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 {
|
|
@@ -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
|
}
|
|
@@ -1,27 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireTimestamp } from '@lota-sdk/shared'
|
|
2
2
|
import { BoundQuery } from 'surrealdb'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
|
-
import { ensureRecordId } from '../../db/record-id'
|
|
5
|
+
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
6
6
|
import type { RecordIdRef } from '../../db/record-id'
|
|
7
7
|
import { databaseService } from '../../db/service'
|
|
8
8
|
import { TABLES } from '../../db/tables'
|
|
9
|
+
import { WorkstreamMessageRowSchema } from '../../db/workstream-message-row'
|
|
10
|
+
import type { WorkstreamMessageRow } from '../../db/workstream-message-row'
|
|
9
11
|
import { normalizeTextBody } from '../../document/parsing'
|
|
10
12
|
|
|
11
|
-
const RecordTimestampSchema = z.union([z.date(), z.string(), z.number()])
|
|
12
|
-
const MessageRoleSchema = z.enum(['system', 'user', 'assistant'])
|
|
13
|
-
const MessagePartSchema = z.record(z.string(), z.unknown())
|
|
14
|
-
const MessageMetadataSchema = z.record(z.string(), z.unknown()).nullish()
|
|
15
|
-
|
|
16
|
-
const WorkstreamMessageRowSchema = z.object({
|
|
17
|
-
id: z.string(),
|
|
18
|
-
workstreamId: z.string(),
|
|
19
|
-
role: MessageRoleSchema,
|
|
20
|
-
parts: z.array(MessagePartSchema).optional(),
|
|
21
|
-
metadata: MessageMetadataSchema,
|
|
22
|
-
createdAt: RecordTimestampSchema,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
13
|
export interface DigestCursor {
|
|
26
14
|
createdAt: Date
|
|
27
15
|
id: string
|
|
@@ -36,14 +24,17 @@ export interface DigestMessage {
|
|
|
36
24
|
cursor: DigestCursor
|
|
37
25
|
}
|
|
38
26
|
|
|
39
|
-
function mapWorkstreamRow(row:
|
|
27
|
+
function mapWorkstreamRow(row: WorkstreamMessageRow): DigestMessage {
|
|
40
28
|
return {
|
|
41
29
|
source: 'workstream',
|
|
42
|
-
sourceId: row.workstreamId,
|
|
30
|
+
sourceId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
|
|
43
31
|
role: row.role,
|
|
44
|
-
parts: row.parts
|
|
32
|
+
parts: row.parts as Array<Record<string, unknown>>,
|
|
45
33
|
metadata: row.metadata ?? undefined,
|
|
46
|
-
cursor: {
|
|
34
|
+
cursor: {
|
|
35
|
+
createdAt: new Date(requireTimestamp(row.createdAt)),
|
|
36
|
+
id: recordIdToString(row.id, TABLES.WORKSTREAM_MESSAGE),
|
|
37
|
+
},
|
|
47
38
|
}
|
|
48
39
|
}
|
|
49
40
|
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
4
4
|
import type { Job, Worker } from 'bullmq'
|
|
5
5
|
|
|
6
6
|
import { chatLogger } from '../config/logger'
|
|
7
|
+
import { truncateText } from '../utils/string'
|
|
7
8
|
|
|
8
9
|
export const DEFAULT_JOB_RETENTION = { removeOnComplete: 200, removeOnFail: 200 }
|
|
9
10
|
export const LOW_JOB_RETENTION = { removeOnComplete: 50, removeOnFail: 50 }
|
|
@@ -32,8 +33,7 @@ interface TracedWorkerJobLike {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function truncateTraceString(value: string, maxChars = MAX_TRACE_STRING_CHARS): string {
|
|
35
|
-
|
|
36
|
-
return `${value.slice(0, maxChars - 3)}...`
|
|
36
|
+
return truncateText(value, maxChars)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function normalizeTraceValue(value: unknown, depth = 0): unknown {
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { tool } from 'ai'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
|
|
4
|
-
export function createLogHelloWorldTool() {
|
|
5
|
-
return tool({
|
|
6
|
-
description: 'Logs "Hello World" to the server console. Requires user approval before running.',
|
|
7
|
-
inputSchema: z.object({
|
|
8
|
-
message: z.string().optional().describe('Optional custom message to log alongside Hello World'),
|
|
9
|
-
}),
|
|
10
|
-
needsApproval: true,
|
|
11
|
-
execute: async ({ message }: { message?: string }) => {
|
|
12
|
-
const output = message ? `Hello World: ${message}` : 'Hello World'
|
|
13
|
-
console.log('[logHelloWorld]', output)
|
|
14
|
-
return { logged: output, timestamp: new Date().toISOString() }
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
}
|