@lota-sdk/core 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- 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/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- 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 +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- 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/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- 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 +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- 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 +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- 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 +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -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 +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- 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 +12 -5
- 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-artifact.service.ts +1 -0
- 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 +386 -58
- package/src/services/plan-helpers.ts +15 -0
- 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 +87 -20
- 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-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- 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 +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +5 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- 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 +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
|
@@ -1,31 +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 getLatestExecutionPlanToolResult(output: unknown): ExecutionPlanToolResultData | undefined {
|
|
15
|
-
if (output && typeof output === 'object' && 'hasPlan' in output) {
|
|
16
|
-
return output as ExecutionPlanToolResultData
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (Array.isArray(output)) {
|
|
20
|
-
for (let index = output.length - 1; index >= 0; index -= 1) {
|
|
21
|
-
const candidate = getLatestExecutionPlanToolResult(output[index])
|
|
22
|
-
if (candidate) return candidate
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return undefined
|
|
27
|
-
}
|
|
28
|
-
|
|
29
15
|
export function createCreateExecutionPlanTool(params: {
|
|
30
16
|
orgId: RecordIdRef
|
|
31
17
|
workstreamId: RecordIdRef
|
|
@@ -41,6 +27,7 @@ export function createCreateExecutionPlanTool(params: {
|
|
|
41
27
|
organizationId: params.orgId,
|
|
42
28
|
workstreamId: params.workstreamId,
|
|
43
29
|
leadAgentId: params.agentId,
|
|
30
|
+
dispatchMode: 'deferred',
|
|
44
31
|
input,
|
|
45
32
|
})
|
|
46
33
|
params.onPlanChanged?.()
|
|
@@ -64,6 +51,7 @@ export function createReplaceExecutionPlanTool(params: {
|
|
|
64
51
|
organizationId: params.orgId,
|
|
65
52
|
workstreamId: params.workstreamId,
|
|
66
53
|
leadAgentId: params.agentId,
|
|
54
|
+
dispatchMode: 'deferred',
|
|
67
55
|
input,
|
|
68
56
|
})
|
|
69
57
|
params.onPlanChanged?.()
|
|
@@ -91,21 +79,31 @@ export function createSubmitExecutionNodeResultTool(params: {
|
|
|
91
79
|
return result
|
|
92
80
|
},
|
|
93
81
|
toModelOutput: ({ output }) => {
|
|
94
|
-
const result =
|
|
82
|
+
const result = getLatestExecutionPlanResult(output)
|
|
95
83
|
const summary = result?.message?.trim()
|
|
96
84
|
return { type: 'text', value: summary && summary.length > 0 ? summary : 'Execution node result submitted.' }
|
|
97
85
|
},
|
|
98
86
|
})
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
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 }) {
|
|
102
99
|
return tool({
|
|
103
100
|
description:
|
|
104
|
-
'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.',
|
|
105
102
|
inputSchema: GetActiveExecutionPlanArgsSchema,
|
|
106
103
|
execute: async (input) =>
|
|
107
104
|
await executionPlanService.getActivePlanToolResult({
|
|
108
105
|
workstreamId: params.workstreamId,
|
|
106
|
+
runId: input.runId,
|
|
109
107
|
includeEvents: input.includeEvents,
|
|
110
108
|
includeArtifacts: input.includeArtifacts,
|
|
111
109
|
includeApprovals: input.includeApprovals,
|
|
@@ -2,10 +2,10 @@ import { tool } from 'ai'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import type { ToolDefinition } from '../ai/definitions'
|
|
5
|
-
import type { Citation } from '../services/workstream.types'
|
|
6
5
|
import { withTimeout } from '../utils/async'
|
|
7
6
|
import { readStringField, truncateOptionalText } from '../utils/string'
|
|
8
7
|
import { getFirecrawlClient } from './firecrawl-client'
|
|
8
|
+
import type { WebCitation } from './tool-contracts'
|
|
9
9
|
|
|
10
10
|
const TOOL_TIMEOUT_MS = 30_000
|
|
11
11
|
const FormatSchema = z.enum(['markdown', 'html', 'rawHtml', 'links', 'images', 'screenshot', 'summary'])
|
|
@@ -14,6 +14,11 @@ const MAX_SUMMARY_CHARS = 1_200
|
|
|
14
14
|
const MAX_LINKS = 25
|
|
15
15
|
const MAX_IMAGES = 10
|
|
16
16
|
|
|
17
|
+
function toRecord(value: unknown): Record<string, unknown> | null {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
19
|
+
return value as Record<string, unknown>
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
function readStringList(record: Record<string, unknown>, key: string, maxItems: number): string[] {
|
|
18
23
|
const value = record[key]
|
|
19
24
|
if (!Array.isArray(value)) return []
|
|
@@ -24,15 +29,12 @@ function readStringList(record: Record<string, unknown>, key: string, maxItems:
|
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
function summarizeDocument(url: string, document: unknown): Record<string, unknown> {
|
|
27
|
-
|
|
32
|
+
const record = toRecord(document)
|
|
33
|
+
if (!record) {
|
|
28
34
|
return { url }
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
const
|
|
32
|
-
const metadata =
|
|
33
|
-
record.metadata && typeof record.metadata === 'object' && !Array.isArray(record.metadata)
|
|
34
|
-
? (record.metadata as Record<string, unknown>)
|
|
35
|
-
: {}
|
|
37
|
+
const metadata = toRecord(record.metadata) ?? {}
|
|
36
38
|
|
|
37
39
|
const canonicalUrl = truncateOptionalText(
|
|
38
40
|
readStringField(metadata, 'url') ?? readStringField(metadata, 'sourceURL') ?? readStringField(record, 'url') ?? url,
|
|
@@ -51,8 +53,8 @@ function summarizeDocument(url: string, document: unknown): Record<string, unkno
|
|
|
51
53
|
return truncateOptionalText(image, 500)
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const imageRecord = toRecord(image)
|
|
57
|
+
if (imageRecord) {
|
|
56
58
|
return truncateOptionalText(readStringField(imageRecord, 'src') ?? readStringField(imageRecord, 'url'), 500)
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -73,18 +75,18 @@ function summarizeDocument(url: string, document: unknown): Record<string, unkno
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
function buildFetchCitations(url: string, document: unknown):
|
|
78
|
+
function buildFetchCitations(url: string, document: unknown): WebCitation[] {
|
|
77
79
|
const fallbackUrl = url.trim()
|
|
78
80
|
let sourceId = fallbackUrl
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (typeof
|
|
85
|
-
sourceId =
|
|
86
|
-
} else if (typeof
|
|
87
|
-
sourceId =
|
|
82
|
+
const docRecord = toRecord(document)
|
|
83
|
+
if (docRecord) {
|
|
84
|
+
const metadataRecord = toRecord(docRecord.metadata)
|
|
85
|
+
if (metadataRecord) {
|
|
86
|
+
if (typeof metadataRecord.url === 'string' && metadataRecord.url.trim().length > 0) {
|
|
87
|
+
sourceId = metadataRecord.url.trim()
|
|
88
|
+
} else if (typeof metadataRecord.sourceURL === 'string' && metadataRecord.sourceURL.trim().length > 0) {
|
|
89
|
+
sourceId = metadataRecord.sourceURL.trim()
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export * from './execution-plan.tool'
|
|
2
2
|
export * from './fetch-webpage.tool'
|
|
3
|
-
export * from './log-hello-world.tool'
|
|
4
3
|
export * from './memory-block.tool'
|
|
5
4
|
export * from './read-file-parts.tool'
|
|
6
5
|
export * from './remember-memory.tool'
|
|
7
6
|
export * from './research-topic.tool'
|
|
8
|
-
export * from './search
|
|
7
|
+
export * from './search.tool'
|
|
9
8
|
export * from './search-web.tool'
|
|
10
9
|
export * from './team-think.tool'
|
|
11
|
-
export * from './tool-
|
|
10
|
+
export * from './tool-contracts'
|
|
12
11
|
export * from './user-questions.tool'
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
|
|
4
4
|
import type { ToolDefinition } from '../ai/definitions'
|
|
5
5
|
import { attachmentStorageService } from '../storage/attachment-storage.service'
|
|
6
|
-
import type { ReadableUploadMetadata } from '../storage/
|
|
6
|
+
import type { ReadableUploadMetadata } from '../storage/attachment-types'
|
|
7
7
|
|
|
8
8
|
const PAGES_PER_PART = 25
|
|
9
9
|
|
|
@@ -2,22 +2,26 @@ import { tool } from 'ai'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import type { ToolDefinition } from '../ai/definitions'
|
|
5
|
-
import type { Citation } from '../services/workstream.types'
|
|
6
5
|
import { withTimeout } from '../utils/async'
|
|
7
6
|
import { readStringField, truncateOptionalText } from '../utils/string'
|
|
8
7
|
import { getFirecrawlClient } from './firecrawl-client'
|
|
8
|
+
import type { WebCitation } from './tool-contracts'
|
|
9
9
|
|
|
10
10
|
const TOOL_TIMEOUT_MS = 30_000
|
|
11
11
|
const SourceSchema = z.enum(['web', 'news', 'images'])
|
|
12
12
|
const MAX_RESULTS_PER_SOURCE = 4
|
|
13
13
|
const MAX_SNIPPET_CHARS = 320
|
|
14
14
|
|
|
15
|
+
function toRecord(value: unknown): Record<string, unknown> | null {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
17
|
+
return value as Record<string, unknown>
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
function readMetadata(item: unknown): Record<string, unknown> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
: {}
|
|
21
|
+
const record = toRecord(item)
|
|
22
|
+
if (!record) return {}
|
|
23
|
+
const metadata = toRecord(record.metadata)
|
|
24
|
+
return metadata ?? {}
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
function readSearchItemSnippet(record: Record<string, unknown>, metadata: Record<string, unknown>): string | undefined {
|
|
@@ -36,9 +40,9 @@ function readSearchItemSnippet(record: Record<string, unknown>, metadata: Record
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
function summarizeSearchItem(item: unknown): Record<string, string> | null {
|
|
39
|
-
|
|
43
|
+
const record = toRecord(item)
|
|
44
|
+
if (!record) return null
|
|
40
45
|
|
|
41
|
-
const record = item as Record<string, unknown>
|
|
42
46
|
const metadata = readMetadata(item)
|
|
43
47
|
const url = extractUrlFromSearchItem(item)
|
|
44
48
|
|
|
@@ -79,9 +83,9 @@ function summarizeSearchItems(items: unknown[] | undefined): Record<string, unkn
|
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
function extractUrlFromSearchItem(item: unknown): string | undefined {
|
|
82
|
-
|
|
86
|
+
const record = toRecord(item)
|
|
87
|
+
if (!record) return undefined
|
|
83
88
|
|
|
84
|
-
const record = item as Record<string, unknown>
|
|
85
89
|
if (typeof record.url === 'string' && record.url.trim().length > 0) {
|
|
86
90
|
return record.url.trim()
|
|
87
91
|
}
|
|
@@ -89,9 +93,8 @@ function extractUrlFromSearchItem(item: unknown): string | undefined {
|
|
|
89
93
|
return record.imageUrl.trim()
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
const metadataRecord = metadata as Record<string, unknown>
|
|
96
|
+
const metadataRecord = toRecord(record.metadata)
|
|
97
|
+
if (metadataRecord) {
|
|
95
98
|
if (typeof metadataRecord.url === 'string' && metadataRecord.url.trim().length > 0) {
|
|
96
99
|
return metadataRecord.url.trim()
|
|
97
100
|
}
|
|
@@ -106,9 +109,9 @@ function extractUrlFromSearchItem(item: unknown): string | undefined {
|
|
|
106
109
|
return undefined
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
function buildWebCitations(results: { web?: unknown[]; news?: unknown[]; images?: unknown[] }):
|
|
112
|
+
function buildWebCitations(results: { web?: unknown[]; news?: unknown[]; images?: unknown[] }): WebCitation[] {
|
|
110
113
|
const seen = new Set<string>()
|
|
111
|
-
const citations:
|
|
114
|
+
const citations: WebCitation[] = []
|
|
112
115
|
const retrievedAt = new Date().toISOString()
|
|
113
116
|
|
|
114
117
|
const append = (items: unknown[] | undefined) => {
|
|
@@ -22,7 +22,7 @@ export function createMemorySearchTool(
|
|
|
22
22
|
const normalizedQuery = query.trim()
|
|
23
23
|
const retrieval = await memoryService.searchAllMemoriesBatched({
|
|
24
24
|
orgId: orgIdString,
|
|
25
|
-
agentName: isAgentName(agentName) ?
|
|
25
|
+
agentName: isAgentName(agentName) ? agentName : undefined,
|
|
26
26
|
query: normalizedQuery,
|
|
27
27
|
...(typeof options?.fastMode === 'boolean' ? { fastMode: options.fastMode } : {}),
|
|
28
28
|
...(typeof options?.allowMultiScopeRerank === 'boolean'
|
|
@@ -9,22 +9,26 @@ import { recordIdToString } from '../db/record-id'
|
|
|
9
9
|
import { TABLES } from '../db/tables'
|
|
10
10
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
11
11
|
import { getRuntimeAdapters } from '../runtime/runtime-extensions'
|
|
12
|
+
import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
|
|
12
13
|
import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation-orchestrator'
|
|
13
14
|
import type { DefaultRepoSections, TeamConsultationParticipantRunner } from '../runtime/team-consultation-orchestrator'
|
|
14
15
|
import { buildTeamConsultationResponseGuard } from '../runtime/team-consultation-prompts'
|
|
15
16
|
import type { ReadableUploadMetadata } from '../services/attachment.service'
|
|
16
17
|
|
|
17
|
-
async function buildTeamThinkAgentTools(
|
|
18
|
+
async function buildTeamThinkAgentTools(
|
|
19
|
+
params: LotaRuntimeTeamThinkToolsParams,
|
|
20
|
+
): Promise<{ tools: Record<string, unknown> }> {
|
|
18
21
|
const builder = getRuntimeAdapters().workstream?.buildTeamThinkAgentTools
|
|
19
22
|
if (!builder) {
|
|
20
23
|
return { tools: {} }
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
const result = await builder(params
|
|
26
|
+
const result = await builder(params)
|
|
24
27
|
return { tools: result.tools as Record<string, unknown> }
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
const TEAM_THINK_AGENT_MAX_RETRIES =
|
|
30
|
+
const TEAM_THINK_AGENT_MAX_RETRIES = 1
|
|
31
|
+
const TEAM_THINK_AGENT_MAX_STEPS = 3
|
|
28
32
|
|
|
29
33
|
export function createTeamThinkTool(params: {
|
|
30
34
|
historyMessages: ChatMessage[]
|
|
@@ -55,7 +59,7 @@ export function createTeamThinkTool(params: {
|
|
|
55
59
|
mode: 'fixedWorkstreamMode',
|
|
56
60
|
onboardingActive: false,
|
|
57
61
|
linearInstalled: false,
|
|
58
|
-
reasoningProfile:
|
|
62
|
+
reasoningProfile: 'fast',
|
|
59
63
|
systemWorkspaceDetails: runParams.systemWorkspaceDetails,
|
|
60
64
|
preSeededMemoriesSection: runParams.preSeededMemoriesSection,
|
|
61
65
|
retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
|
|
@@ -72,20 +76,22 @@ export function createTeamThinkTool(params: {
|
|
|
72
76
|
workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
|
|
73
77
|
workstreamId: params.workstreamId,
|
|
74
78
|
githubInstalled: params.githubInstalled,
|
|
75
|
-
provideRepoTool: params.provideRepoTool,
|
|
79
|
+
provideRepoTool: agentId !== 'mentor' && params.provideRepoTool,
|
|
76
80
|
availableUploads: params.availableUploads,
|
|
77
81
|
defaultRepoSections: params.defaultRepoSectionsByAgent[agentId],
|
|
78
82
|
context: params.context,
|
|
79
83
|
toolProviders: params.toolProviders,
|
|
80
84
|
})
|
|
81
85
|
const agentConfig = config as Record<string, unknown>
|
|
82
|
-
const
|
|
83
|
-
const
|
|
86
|
+
const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : agentId
|
|
87
|
+
const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
|
|
88
|
+
const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
|
|
89
|
+
const agent = createAgent[agentId_]({
|
|
84
90
|
mode: 'fixedWorkstreamMode',
|
|
85
91
|
tools,
|
|
86
92
|
extraInstructions: agentConfig.extraInstructions,
|
|
87
93
|
maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
|
|
88
|
-
stopWhen: [stepCountIs(
|
|
94
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
89
95
|
})
|
|
90
96
|
const observer = {
|
|
91
97
|
run: async <T>(fn: () => T | Promise<T>): Promise<T> => fn(),
|
|
@@ -7,7 +7,6 @@ export const MutatingApprovalSchema = {
|
|
|
7
7
|
approvalMessageId: z.string().trim().min(1).max(200).optional(),
|
|
8
8
|
} as const
|
|
9
9
|
|
|
10
|
-
/** @lintignore */
|
|
11
10
|
export const CitationSchema = z
|
|
12
11
|
.object({
|
|
13
12
|
source: z.string().trim().min(1),
|
|
@@ -17,5 +16,13 @@ export const CitationSchema = z
|
|
|
17
16
|
})
|
|
18
17
|
.strict()
|
|
19
18
|
|
|
20
|
-
/** @lintignore */
|
|
21
19
|
export type Citation = z.infer<typeof CitationSchema>
|
|
20
|
+
|
|
21
|
+
export interface WebCitation {
|
|
22
|
+
title?: string
|
|
23
|
+
url?: string
|
|
24
|
+
snippet?: string
|
|
25
|
+
source?: string
|
|
26
|
+
sourceId?: string
|
|
27
|
+
retrievedAt?: string
|
|
28
|
+
}
|
package/src/utils/async.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serverLogger } from '../config/logger'
|
|
2
|
+
import { getErrorMessage } from './errors'
|
|
2
3
|
|
|
3
4
|
class TimeoutError extends Error {
|
|
4
5
|
constructor(operation: string, ms: number) {
|
|
@@ -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,3 +1,9 @@
|
|
|
1
|
+
export { getErrorMessage } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
export function toError(value: unknown): Error {
|
|
4
|
+
return value instanceof Error ? value : new Error(String(value))
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
export class AppError extends Error {
|
|
2
8
|
public readonly code: string
|
|
3
9
|
public readonly statusCode: number
|
|
@@ -26,3 +32,44 @@ export class BadRequestError extends AppError {
|
|
|
26
32
|
super(message, 'BAD_REQUEST', 400)
|
|
27
33
|
}
|
|
28
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
|
|
@@ -2,8 +2,7 @@ import type { ErrorHandler } from 'hono'
|
|
|
2
2
|
import { HTTPException } from 'hono/http-exception'
|
|
3
3
|
import { ZodError } from 'zod'
|
|
4
4
|
|
|
5
|
-
import { getErrorMessage } from './
|
|
6
|
-
import { AppError } from './errors'
|
|
5
|
+
import { AppError, getErrorMessage } from './errors'
|
|
7
6
|
|
|
8
7
|
type AppErrorLike = Pick<AppError, 'code' | 'message' | 'statusCode' | 'toResponse'> & { name?: string }
|
|
9
8
|
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
export * from './async'
|
|
2
2
|
export * from './date-time'
|
|
3
|
-
export * from './
|
|
3
|
+
export * from './env'
|
|
4
4
|
export * from './errors'
|
|
5
5
|
export * from './hono-error-handler'
|
|
6
|
-
export * from './
|
|
6
|
+
export * from './sse-keepalive'
|
|
7
|
+
export {
|
|
8
|
+
CHARS_PER_TOKEN_ESTIMATE,
|
|
9
|
+
clampImportance,
|
|
10
|
+
compactRecord,
|
|
11
|
+
compactWhitespace,
|
|
12
|
+
isRecord,
|
|
13
|
+
parseLineList,
|
|
14
|
+
readRecordArray,
|
|
15
|
+
readString,
|
|
16
|
+
readStringArray,
|
|
17
|
+
readStringField,
|
|
18
|
+
slugify,
|
|
19
|
+
stringifyLineList,
|
|
20
|
+
stringifyUnknown,
|
|
21
|
+
truncateOptionalText,
|
|
22
|
+
truncateText,
|
|
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
|
/**
|
|
@@ -49,3 +49,130 @@ export function truncateOptionalText(value: string | undefined, maxChars: number
|
|
|
49
49
|
export function compactWhitespace(value: string): string {
|
|
50
50
|
return value.trim().replace(/\s+/g, ' ')
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns the value as a plain record if it is a non-null, non-array object,
|
|
55
|
+
* or null otherwise.
|
|
56
|
+
*/
|
|
57
|
+
export function readRecord(value: unknown): Record<string, unknown> | null {
|
|
58
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
59
|
+
return value as Record<string, unknown>
|
|
60
|
+
}
|
|
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
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Rough character-to-token estimate used for context budget calculations.
|
|
84
|
+
*/
|
|
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
|
+
}
|