@lota-sdk/core 0.1.9 → 0.1.12
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_workstream.surql +1 -0
- package/infrastructure/schema/02_execution_plan.surql +202 -52
- package/package.json +4 -87
- package/src/ai/index.ts +3 -0
- package/src/bifrost/bifrost.ts +94 -25
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +30 -7
- package/src/config/constants.ts +0 -9
- package/src/config/debug-logger.ts +43 -0
- package/src/config/index.ts +5 -0
- package/src/config/model-constants.ts +8 -9
- package/src/config/workstream-defaults.ts +4 -0
- package/src/db/cursor-pagination.ts +2 -2
- package/src/db/index.ts +10 -0
- package/src/db/memory-store.ts +3 -71
- package/src/db/memory.ts +9 -15
- package/src/db/service.ts +42 -2
- package/src/db/tables.ts +9 -2
- package/src/document/index.ts +2 -0
- package/src/document/parsing.ts +0 -25
- package/src/embeddings/provider.ts +102 -22
- package/src/index.ts +15 -499
- package/src/queues/index.ts +10 -0
- package/src/redis/connection-accessor.ts +26 -0
- package/src/redis/connection.ts +1 -1
- package/src/redis/index.ts +9 -25
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -1
- package/src/redis/stream-context.ts +54 -0
- package/src/runtime/agent-runtime-policy.ts +9 -5
- package/src/runtime/agent-stream-helpers.ts +6 -3
- package/src/runtime/agent-types.ts +1 -5
- package/src/runtime/approval-continuation.ts +68 -1
- package/src/runtime/chat-attachments.ts +1 -1
- package/src/runtime/chat-request-routing.ts +6 -2
- package/src/runtime/context-compaction-runtime.ts +2 -2
- package/src/runtime/context-compaction.ts +1 -1
- package/src/runtime/execution-plan.ts +22 -15
- package/src/runtime/index.ts +26 -0
- package/src/runtime/indexed-repositories-policy.ts +10 -10
- package/src/runtime/memory-pipeline.ts +0 -2
- package/src/runtime/runtime-config.ts +238 -0
- package/src/runtime/runtime-extensions.ts +3 -2
- package/src/runtime/runtime-worker-registry.ts +47 -0
- package/src/runtime/team-consultation-orchestrator.ts +9 -6
- package/src/runtime/team-consultation-prompts.ts +3 -2
- package/src/runtime/turn-lifecycle.ts +13 -5
- package/src/runtime/workstream-chat-helpers.ts +0 -54
- package/src/runtime/workstream-routing-policy.ts +3 -7
- package/src/runtime.ts +387 -0
- package/src/services/chat-attachments.service.ts +1 -1
- package/src/services/context-compaction.service.ts +1 -1
- package/src/services/document-chunk.service.ts +2 -2
- package/src/services/execution-plan.service.ts +584 -793
- package/src/services/index.ts +14 -0
- package/src/services/learned-skill.service.ts +82 -39
- package/src/services/memory.service.ts +5 -4
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +1 -1
- package/src/services/plan-approval.service.ts +83 -0
- package/src/services/plan-artifact.service.ts +44 -0
- package/src/services/plan-builder.service.ts +61 -0
- package/src/services/plan-checkpoint.service.ts +53 -0
- package/src/services/plan-compiler.service.ts +81 -0
- package/src/services/plan-executor.service.ts +1624 -0
- package/src/services/plan-run.service.ts +422 -0
- package/src/services/plan-validator.service.ts +760 -0
- package/src/services/recent-activity-title.service.ts +1 -1
- package/src/services/recent-activity.service.ts +14 -16
- package/src/services/user.service.ts +2 -2
- package/src/services/workstream-message.service.ts +2 -3
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.ts +156 -59
- package/src/services/workstream-turn.ts +26 -1
- package/src/services/workstream.service.ts +35 -9
- package/src/services/workstream.types.ts +1 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +11 -10
- package/src/storage/generated-document-storage.service.ts +7 -6
- package/src/storage/index.ts +10 -0
- package/src/system-agents/delegated-agent-factory.ts +78 -29
- package/src/system-agents/index.ts +4 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +2 -4
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +22 -48
- package/src/tools/firecrawl-client.ts +2 -2
- package/src/tools/index.ts +12 -0
- package/src/tools/log-hello-world.tool.ts +17 -0
- package/src/tools/research-topic.tool.ts +1 -1
- package/src/tools/team-think.tool.ts +1 -1
- package/src/tools/user-questions.tool.ts +2 -2
- package/src/utils/index.ts +6 -0
- package/src/workers/bootstrap.ts +8 -16
- package/src/workers/index.ts +7 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
- package/src/workers/skill-extraction.runner.ts +3 -3
- package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
- package/src/workers/utils/repo-structure-extractor.ts +2 -5
- package/src/workers/utils/repomix-file-sections.ts +42 -0
- package/src/config/env-shapes.ts +0 -121
- package/src/runtime/agent-contract.ts +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WORKSTREAM } from '@lota-sdk/shared
|
|
1
|
+
import { WORKSTREAM } from '@lota-sdk/shared'
|
|
2
2
|
import { BoundQuery, RecordId, StringRecordId, surql } from 'surrealdb'
|
|
3
3
|
|
|
4
4
|
import { agentDisplayNames, getCoreWorkstreamProfile, isAgentName } from '../config/agent-defaults'
|
|
@@ -345,19 +345,19 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
345
345
|
|
|
346
346
|
const onboardingWelcome = bootstrapConfig.onboardingWelcome
|
|
347
347
|
if (!onboardingCompleted && onboardingWelcome) {
|
|
348
|
-
const
|
|
348
|
+
const createdOnboardingOwnerWorkstream = createdWorkstreams.find(
|
|
349
349
|
(workstream) => workstream.mode === 'direct' && workstream.agentId === onboardingWelcome.directAgentId,
|
|
350
350
|
)
|
|
351
|
-
const
|
|
351
|
+
const existingOnboardingOwnerWorkstream = directWorkstreamsByAgent.get(onboardingWelcome.directAgentId)
|
|
352
352
|
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
(
|
|
353
|
+
const onboardingOwnerWorkstreamId =
|
|
354
|
+
createdOnboardingOwnerWorkstream?.id ??
|
|
355
|
+
(existingOnboardingOwnerWorkstream ? this.normalizeWorkstreamId(existingOnboardingOwnerWorkstream.id) : null)
|
|
356
356
|
|
|
357
|
-
if (
|
|
358
|
-
const
|
|
357
|
+
if (onboardingOwnerWorkstreamId) {
|
|
358
|
+
const onboardingOwnerWorkstreamRef = ensureRecordId(onboardingOwnerWorkstreamId, TABLES.WORKSTREAM)
|
|
359
359
|
await workstreamMessageService.ensureBootstrapWelcomeMessage({
|
|
360
|
-
workstreamId:
|
|
360
|
+
workstreamId: onboardingOwnerWorkstreamRef,
|
|
361
361
|
agentId: onboardingWelcome.directAgentId,
|
|
362
362
|
text: onboardingWelcome.buildMessageText({ userName: options?.userName }),
|
|
363
363
|
})
|
|
@@ -504,6 +504,32 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
504
504
|
await this.setActiveRunId(workstreamId, null)
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
async setActiveStreamId(workstreamId: RecordIdRef, streamId: string): Promise<void> {
|
|
508
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
509
|
+
await databaseService.query<unknown>(surql`
|
|
510
|
+
UPDATE ONLY ${workstreamRef}
|
|
511
|
+
SET activeStreamId = ${streamId}
|
|
512
|
+
`)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async getActiveStreamId(workstreamId: RecordIdRef): Promise<string | null> {
|
|
516
|
+
const workstream = await this.getById(workstreamId)
|
|
517
|
+
const activeStreamId = workstream.activeStreamId
|
|
518
|
+
if (typeof activeStreamId !== 'string') return null
|
|
519
|
+
const normalized = activeStreamId.trim()
|
|
520
|
+
return normalized.length > 0 ? normalized : null
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async clearActiveStreamIdIfMatches(workstreamId: RecordIdRef, streamId: string): Promise<void> {
|
|
524
|
+
const activeStreamId = await this.getActiveStreamId(workstreamId)
|
|
525
|
+
if (activeStreamId !== streamId) return
|
|
526
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
527
|
+
await databaseService.query<unknown>(surql`
|
|
528
|
+
UPDATE ONLY ${workstreamRef}
|
|
529
|
+
SET activeStreamId = NONE
|
|
530
|
+
`)
|
|
531
|
+
}
|
|
532
|
+
|
|
507
533
|
async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
|
|
508
534
|
const activeRunId = await this.getActiveRunId(workstreamId)
|
|
509
535
|
if (!activeRunId) return false
|
|
@@ -42,6 +42,7 @@ export const WorkstreamSchema = z.object({
|
|
|
42
42
|
memoryBlock: z.string().nullish(),
|
|
43
43
|
memoryBlockSummary: z.string().nullish(),
|
|
44
44
|
activeRunId: z.string().nullish(),
|
|
45
|
+
activeStreamId: z.string().nullish(),
|
|
45
46
|
compactionSummary: z.string().nullish(),
|
|
46
47
|
lastCompactedMessageId: z.string().nullish(),
|
|
47
48
|
nameGenerated: z.boolean().optional().default(false),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SUPPORTED_ATTACHMENT_MIME_TYPES, getAttachmentExtension } from '@lota-sdk/shared
|
|
1
|
+
import { SUPPORTED_ATTACHMENT_MIME_TYPES, getAttachmentExtension } from '@lota-sdk/shared'
|
|
2
2
|
import mammoth from 'mammoth'
|
|
3
3
|
import { PDFParse } from 'pdf-parse'
|
|
4
4
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { inferContentType } from '@lota-sdk/shared
|
|
1
|
+
import { inferContentType } from '@lota-sdk/shared'
|
|
2
2
|
import { S3Client } from 'bun'
|
|
3
3
|
|
|
4
|
-
import { env } from '../config/env-shapes'
|
|
5
4
|
import { serverLogger } from '../config/logger'
|
|
5
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
6
6
|
import { readString } from '../utils/string'
|
|
7
7
|
import {
|
|
8
8
|
extractAttachmentText,
|
|
@@ -41,17 +41,18 @@ export class AttachmentStorageService {
|
|
|
41
41
|
private readonly client: S3Client
|
|
42
42
|
|
|
43
43
|
constructor() {
|
|
44
|
+
const config = getRuntimeConfig()
|
|
44
45
|
this.client = new S3Client({
|
|
45
|
-
accessKeyId:
|
|
46
|
-
secretAccessKey:
|
|
47
|
-
bucket:
|
|
48
|
-
endpoint:
|
|
49
|
-
region:
|
|
46
|
+
accessKeyId: config.s3.accessKeyId,
|
|
47
|
+
secretAccessKey: config.s3.secretAccessKey,
|
|
48
|
+
bucket: config.s3.bucket,
|
|
49
|
+
endpoint: config.s3.endpoint,
|
|
50
|
+
region: config.s3.region,
|
|
50
51
|
})
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
getAttachmentUrl(storageKey: string): string {
|
|
54
|
-
return this.client.file(storageKey).presign({ expiresIn:
|
|
55
|
+
return this.client.file(storageKey).presign({ expiresIn: getRuntimeConfig().s3.attachmentUrlExpiresIn })
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
async writeOrganizationDocument({
|
|
@@ -102,7 +103,7 @@ export class AttachmentStorageService {
|
|
|
102
103
|
sizeBytes: file.size,
|
|
103
104
|
storageKey,
|
|
104
105
|
url: this.getAttachmentUrl(storageKey),
|
|
105
|
-
expiresInSeconds:
|
|
106
|
+
expiresInSeconds: getRuntimeConfig().s3.attachmentUrlExpiresIn,
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -128,7 +129,7 @@ export class AttachmentStorageService {
|
|
|
128
129
|
sizeBytes,
|
|
129
130
|
storageKey,
|
|
130
131
|
url: this.getAttachmentUrl(storageKey),
|
|
131
|
-
expiresInSeconds:
|
|
132
|
+
expiresInSeconds: getRuntimeConfig().s3.attachmentUrlExpiresIn,
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { S3Client } from 'bun'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
4
4
|
|
|
5
5
|
function toSafeSegment(value: string): string {
|
|
6
6
|
return value
|
|
@@ -27,12 +27,13 @@ class GeneratedDocumentStorageService {
|
|
|
27
27
|
|
|
28
28
|
private get client(): InstanceType<typeof S3Client> {
|
|
29
29
|
if (!this._client) {
|
|
30
|
+
const config = getRuntimeConfig()
|
|
30
31
|
this._client = new S3Client({
|
|
31
|
-
accessKeyId:
|
|
32
|
-
secretAccessKey:
|
|
33
|
-
bucket:
|
|
34
|
-
endpoint:
|
|
35
|
-
region:
|
|
32
|
+
accessKeyId: config.s3.accessKeyId,
|
|
33
|
+
secretAccessKey: config.s3.secretAccessKey,
|
|
34
|
+
bucket: config.s3.bucket,
|
|
35
|
+
endpoint: config.s3.endpoint,
|
|
36
|
+
region: config.s3.region,
|
|
36
37
|
})
|
|
37
38
|
}
|
|
38
39
|
return this._client
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './attachment-parser'
|
|
2
|
+
export * from './attachment-storage.service'
|
|
3
|
+
export * from './generated-document-storage.service'
|
|
4
|
+
export type { MessagePartLike, ReadableUploadPageMode, ReadableUploadPagePart } from './attachments.types'
|
|
5
|
+
export {
|
|
6
|
+
buildOrganizationDocumentStorageKey,
|
|
7
|
+
buildUploadStorageKey,
|
|
8
|
+
buildUploadStoragePrefix,
|
|
9
|
+
readNonNegativeInteger,
|
|
10
|
+
} from './attachments.utils'
|
|
@@ -7,11 +7,12 @@ import { isRecord } from '../utils/string'
|
|
|
7
7
|
import { assertSubstantiveAgentResult } from './agent-result'
|
|
8
8
|
|
|
9
9
|
type AgentProviderOptions = ToolLoopAgentSettings['providerOptions']
|
|
10
|
+
type AgentModel = LanguageModel | (() => LanguageModel)
|
|
10
11
|
|
|
11
12
|
interface DelegatedAgentDefinition {
|
|
12
13
|
id: string
|
|
13
14
|
description: string
|
|
14
|
-
model:
|
|
15
|
+
model: AgentModel
|
|
15
16
|
providerOptions?: AgentProviderOptions
|
|
16
17
|
instructions: string
|
|
17
18
|
tools?: ToolSet
|
|
@@ -24,6 +25,10 @@ interface DelegatedAgentDefinitionWithContext<TContext> extends Omit<DelegatedAg
|
|
|
24
25
|
createTools: (context: TContext) => ToolSet
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function resolveAgentModel(model: AgentModel): LanguageModel {
|
|
29
|
+
return typeof model === 'function' ? model() : model
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
function buildCurrentDateContext(now = new Date()): string {
|
|
28
33
|
const isoDate = now.toISOString().slice(0, 10)
|
|
29
34
|
const humanDate = new Intl.DateTimeFormat('en-US', {
|
|
@@ -75,6 +80,9 @@ When your analysis is complete, return your final answer directly as markdown te
|
|
|
75
80
|
</termination>`
|
|
76
81
|
|
|
77
82
|
const MAX_RETAINED_AGENT_MESSAGES = 10
|
|
83
|
+
const MAX_NON_SUBSTANTIVE_AGENT_RESULT_ATTEMPTS = 2
|
|
84
|
+
const NON_SUBSTANTIVE_AGENT_RESULT_RETRY_PROMPT =
|
|
85
|
+
'Return a complete substantive markdown answer. Do not reply with an empty result, placeholder, or tool-only outcome.'
|
|
78
86
|
|
|
79
87
|
export function retainCriticalAgentMessages(messages: ModelMessage[]): ModelMessage[] {
|
|
80
88
|
const systemMessages = messages.filter((message) => message.role === 'system')
|
|
@@ -112,6 +120,31 @@ function resolveDelegatedAgentTemperature(definition: {
|
|
|
112
120
|
return definition.temperature ?? 0.2
|
|
113
121
|
}
|
|
114
122
|
|
|
123
|
+
async function generateSubstantiveDelegatedAgentResult(params: {
|
|
124
|
+
label: string
|
|
125
|
+
task: string
|
|
126
|
+
abortSignal?: AbortSignal
|
|
127
|
+
createAgent: () => ToolLoopAgent<never, ToolSet>
|
|
128
|
+
}): Promise<string> {
|
|
129
|
+
let lastError: unknown
|
|
130
|
+
|
|
131
|
+
for (let attempt = 0; attempt < MAX_NON_SUBSTANTIVE_AGENT_RESULT_ATTEMPTS; attempt += 1) {
|
|
132
|
+
const prompt = attempt === 0 ? params.task : `${params.task}\n\n${NON_SUBSTANTIVE_AGENT_RESULT_RETRY_PROMPT}`
|
|
133
|
+
const result = await params.createAgent().generate({ prompt, abortSignal: params.abortSignal })
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
return assertSubstantiveAgentResult(result.text, params.label)
|
|
137
|
+
} catch (error) {
|
|
138
|
+
lastError = error
|
|
139
|
+
if (params.abortSignal?.aborted) {
|
|
140
|
+
throw error
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw lastError ?? new Error(`${params.label} must contain substantive text.`)
|
|
146
|
+
}
|
|
147
|
+
|
|
115
148
|
export function createDelegatedAgentTool(definition: DelegatedAgentDefinition): ToolDefinition<void> {
|
|
116
149
|
const maxSteps = definition.maxSteps ?? 10
|
|
117
150
|
const temperature = resolveDelegatedAgentTemperature(definition)
|
|
@@ -124,20 +157,28 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
|
|
|
124
157
|
inputSchema: z.object({ task: z.string().min(1) }),
|
|
125
158
|
execute: async ({ task }: { task: string }, { abortSignal }) => {
|
|
126
159
|
const agentTools = definition.tools
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return {
|
|
160
|
+
const createAgent = () =>
|
|
161
|
+
new ToolLoopAgent({
|
|
162
|
+
id: definition.id,
|
|
163
|
+
model: resolveAgentModel(definition.model),
|
|
164
|
+
...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
|
|
165
|
+
instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
|
|
166
|
+
tools: agentTools,
|
|
167
|
+
maxOutputTokens: definition.maxOutputTokens ?? 4096,
|
|
168
|
+
...(typeof temperature === 'number' ? { temperature } : {}),
|
|
169
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
170
|
+
prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
task,
|
|
175
|
+
result: await generateSubstantiveDelegatedAgentResult({
|
|
176
|
+
label: `${definition.id} result`,
|
|
177
|
+
task,
|
|
178
|
+
abortSignal,
|
|
179
|
+
createAgent,
|
|
180
|
+
}),
|
|
181
|
+
}
|
|
141
182
|
},
|
|
142
183
|
}),
|
|
143
184
|
} as const satisfies ToolDefinition<void>
|
|
@@ -157,20 +198,28 @@ export function createDelegatedAgentToolWithContext<TContext>(
|
|
|
157
198
|
inputSchema: z.object({ task: z.string().min(1) }),
|
|
158
199
|
execute: async ({ task }: { task: string }, { abortSignal }) => {
|
|
159
200
|
const agentTools = definition.createTools(context)
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return {
|
|
201
|
+
const createAgent = () =>
|
|
202
|
+
new ToolLoopAgent({
|
|
203
|
+
id: definition.id,
|
|
204
|
+
model: resolveAgentModel(definition.model),
|
|
205
|
+
...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
|
|
206
|
+
instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
|
|
207
|
+
tools: agentTools,
|
|
208
|
+
maxOutputTokens: definition.maxOutputTokens ?? 4096,
|
|
209
|
+
...(typeof temperature === 'number' ? { temperature } : {}),
|
|
210
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
211
|
+
prepareStep: async ({ messages }) => ({ messages: retainCriticalAgentMessages(messages) }),
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
task,
|
|
216
|
+
result: await generateSubstantiveDelegatedAgentResult({
|
|
217
|
+
label: `${definition.id} result`,
|
|
218
|
+
task,
|
|
219
|
+
abortSignal,
|
|
220
|
+
createAgent,
|
|
221
|
+
}),
|
|
222
|
+
}
|
|
174
223
|
},
|
|
175
224
|
}),
|
|
176
225
|
} as const satisfies ToolDefinition<TContext>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolLoopAgent } from 'ai'
|
|
2
2
|
|
|
3
3
|
import { bifrostModel } from '../bifrost/bifrost'
|
|
4
|
+
import { getLeadAgentDisplayName } from '../config/agent-defaults'
|
|
4
5
|
import {
|
|
5
6
|
OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
|
|
6
7
|
OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
@@ -10,8 +11,42 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
|
|
|
10
11
|
|
|
11
12
|
const RECENT_ACTIVITY_TITLE_MAX_TOKENS = 256
|
|
12
13
|
|
|
14
|
+
export function buildRecentActivityTitleRefinerPrompt(): string {
|
|
15
|
+
const leadAgentDisplayName = getLeadAgentDisplayName() || 'the lead agent'
|
|
16
|
+
|
|
17
|
+
return `<agent-instructions>
|
|
18
|
+
You are ${leadAgentDisplayName} writing the visible title for a recent activity item.
|
|
19
|
+
|
|
20
|
+
<goal>
|
|
21
|
+
Turn recent activity context into a short label that helps the user quickly recognize and reopen the task.
|
|
22
|
+
</goal>
|
|
23
|
+
|
|
24
|
+
<rules>
|
|
25
|
+
- Write 4-10 words when possible.
|
|
26
|
+
- Prefer concrete task language over generic labels.
|
|
27
|
+
- Mention the agent only when it adds useful context.
|
|
28
|
+
- Focus on what the user was trying to accomplish.
|
|
29
|
+
- Avoid generic phrases like "Chat", "Conversation", "Recent activity", "Task", or "Workstream update".
|
|
30
|
+
- Avoid punctuation at the end.
|
|
31
|
+
- Do not invent details that are not present in the input.
|
|
32
|
+
- If the input is too weak for improvement, return the current system title.
|
|
33
|
+
</rules>
|
|
34
|
+
|
|
35
|
+
<examples>
|
|
36
|
+
- "Review pending decisions with ${leadAgentDisplayName}"
|
|
37
|
+
- "Extract website intelligence findings"
|
|
38
|
+
- "Discuss onboarding repository indexing"
|
|
39
|
+
- "Review PRD from GitHub indexing"
|
|
40
|
+
</examples>
|
|
41
|
+
|
|
42
|
+
<output>
|
|
43
|
+
Return only the title text. No quotes, labels, JSON, markdown, or explanation.
|
|
44
|
+
</output>
|
|
45
|
+
</agent-instructions>`
|
|
46
|
+
}
|
|
47
|
+
|
|
13
48
|
export const recentActivityTitleRefinerPrompt = `<agent-instructions>
|
|
14
|
-
You are the
|
|
49
|
+
You are the lead agent writing the visible title for a recent activity item.
|
|
15
50
|
|
|
16
51
|
<goal>
|
|
17
52
|
Turn recent activity context into a short label that helps the user quickly recognize and reopen the task.
|
|
@@ -29,7 +64,7 @@ Turn recent activity context into a short label that helps the user quickly reco
|
|
|
29
64
|
</rules>
|
|
30
65
|
|
|
31
66
|
<examples>
|
|
32
|
-
- "Review pending decisions with
|
|
67
|
+
- "Review pending decisions with the lead agent"
|
|
33
68
|
- "Extract website intelligence findings"
|
|
34
69
|
- "Discuss onboarding repository indexing"
|
|
35
70
|
- "Review PRD from GitHub indexing"
|
|
@@ -46,7 +81,7 @@ export function createRecentActivityTitleRefinerAgent(options: CreateHelperToolL
|
|
|
46
81
|
model: bifrostModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
|
|
47
82
|
providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
|
|
48
83
|
...resolveHelperAgentOptions(options, {
|
|
49
|
-
instructions:
|
|
84
|
+
instructions: buildRecentActivityTitleRefinerPrompt(),
|
|
50
85
|
maxOutputTokens: RECENT_ACTIVITY_TITLE_MAX_TOKENS,
|
|
51
86
|
}),
|
|
52
87
|
})
|
|
@@ -10,7 +10,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
|
|
|
10
10
|
|
|
11
11
|
const REGULAR_CHAT_MEMORY_DIGEST_MAX_TOKENS = 8_192
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const regularChatMemoryDigestPrompt = `<agent-instructions>
|
|
14
14
|
You are the regular-chat memory digest synthesizer.
|
|
15
15
|
|
|
16
16
|
<goal>
|
|
@@ -11,7 +11,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
|
|
|
11
11
|
|
|
12
12
|
const SKILL_EXTRACTOR_MAX_TOKENS = 8_192
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const skillExtractorPrompt = `<agent-instructions>
|
|
15
15
|
You are the skill extractor.
|
|
16
16
|
|
|
17
17
|
<goal>
|
|
@@ -11,7 +11,7 @@ import { resolveHelperAgentOptions } from './helper-agent-options'
|
|
|
11
11
|
|
|
12
12
|
const SKILL_MANAGER_MAX_TOKENS = 4_096
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const skillManagerPrompt = `<agent-instructions>
|
|
15
15
|
You are the skill manager.
|
|
16
16
|
|
|
17
17
|
<goal>
|
|
@@ -49,7 +49,7 @@ Return a JSON object with:
|
|
|
49
49
|
</output-contract>
|
|
50
50
|
</agent-instructions>`
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const MergedSkillSchema = z.object({
|
|
53
53
|
name: z.string(),
|
|
54
54
|
description: z.string(),
|
|
55
55
|
instructions: z.string(),
|
|
@@ -65,8 +65,6 @@ export const SkillManagerOutputSchema = z.object({
|
|
|
65
65
|
mergedSkill: MergedSkillSchema.optional(),
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
export type SkillManagerOutput = z.infer<typeof SkillManagerOutputSchema>
|
|
69
|
-
|
|
70
68
|
export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOptions) {
|
|
71
69
|
return new ToolLoopAgent({
|
|
72
70
|
id: 'skill-manager',
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type { CreateHelperToolLoopAgentOptions } from '../runtime/agent-types'
|
|
9
9
|
import { resolveHelperAgentOptions } from './helper-agent-options'
|
|
10
10
|
|
|
11
|
-
const WORKSTREAM_TITLE_MAX_TOKENS =
|
|
11
|
+
const WORKSTREAM_TITLE_MAX_TOKENS = 512
|
|
12
12
|
|
|
13
13
|
export const WORKSTREAM_TITLE_GENERATOR_PROMPT = `<agent-instructions>
|
|
14
14
|
You are a **Title Generator** that creates concise chat titles.
|
|
@@ -18,7 +18,7 @@ Generate a chat title based only on the user's message.
|
|
|
18
18
|
</task>
|
|
19
19
|
|
|
20
20
|
<constraints>
|
|
21
|
-
- Maximum 4
|
|
21
|
+
- Maximum 3-4 words
|
|
22
22
|
- Capture the core workstream or intent
|
|
23
23
|
- Use natural, readable language
|
|
24
24
|
- No punctuation at the end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CreateExecutionPlanArgsSchema,
|
|
3
3
|
GetActiveExecutionPlanArgsSchema,
|
|
4
|
-
|
|
4
|
+
ResumeExecutionPlanRunArgsSchema,
|
|
5
5
|
ReplaceExecutionPlanArgsSchema,
|
|
6
|
-
|
|
7
|
-
} from '@lota-sdk/shared
|
|
8
|
-
import type { ExecutionPlanToolResultData } from '@lota-sdk/shared
|
|
6
|
+
SubmitExecutionNodeResultArgsSchema,
|
|
7
|
+
} from '@lota-sdk/shared'
|
|
8
|
+
import type { ExecutionPlanToolResultData } from '@lota-sdk/shared'
|
|
9
9
|
import { tool } from 'ai'
|
|
10
10
|
|
|
11
11
|
import type { RecordIdRef } from '../db/record-id'
|
|
@@ -34,7 +34,7 @@ export function createCreateExecutionPlanTool(params: {
|
|
|
34
34
|
}) {
|
|
35
35
|
return tool({
|
|
36
36
|
description:
|
|
37
|
-
'Create a
|
|
37
|
+
'Create a contract-driven execution plan for this workstream. Nodes must define deliverables, success criteria, completion checks, and executor policy.',
|
|
38
38
|
inputSchema: CreateExecutionPlanArgsSchema,
|
|
39
39
|
execute: async (input) => {
|
|
40
40
|
const result = await executionPlanService.createPlan({
|
|
@@ -56,7 +56,8 @@ export function createReplaceExecutionPlanTool(params: {
|
|
|
56
56
|
onPlanChanged?: () => void
|
|
57
57
|
}) {
|
|
58
58
|
return tool({
|
|
59
|
-
description:
|
|
59
|
+
description:
|
|
60
|
+
'Replace the active execution run with a new contract-driven plan when the graph, contracts, or approach materially change.',
|
|
60
61
|
inputSchema: ReplaceExecutionPlanArgsSchema,
|
|
61
62
|
execute: async (input) => {
|
|
62
63
|
const result = await executionPlanService.replacePlan({
|
|
@@ -71,48 +72,17 @@ export function createReplaceExecutionPlanTool(params: {
|
|
|
71
72
|
})
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
export function
|
|
75
|
+
export function createSubmitExecutionNodeResultTool(params: {
|
|
75
76
|
workstreamId: RecordIdRef
|
|
76
77
|
agentId: string
|
|
77
78
|
onPlanChanged?: () => void
|
|
78
79
|
}) {
|
|
79
80
|
return tool({
|
|
80
81
|
description:
|
|
81
|
-
'
|
|
82
|
-
inputSchema:
|
|
83
|
-
execute: async
|
|
84
|
-
const
|
|
85
|
-
workstreamId: params.workstreamId,
|
|
86
|
-
includeEvents: true,
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
if (activePlan.plan && activePlan.plan.planId === input.planId) {
|
|
90
|
-
const optimisticPlan = structuredClone(activePlan.plan)
|
|
91
|
-
const targetTask = optimisticPlan.tasks.find((task) => task.id === input.taskId)
|
|
92
|
-
|
|
93
|
-
if (targetTask) {
|
|
94
|
-
targetTask.status = input.status
|
|
95
|
-
if (input.resultStatus !== undefined) targetTask.resultStatus = input.resultStatus
|
|
96
|
-
if (input.outputSummary !== undefined) targetTask.outputSummary = input.outputSummary || undefined
|
|
97
|
-
if (input.blockedReason !== undefined) targetTask.blockedReason = input.blockedReason || undefined
|
|
98
|
-
if (input.externalRef !== undefined) targetTask.externalRef = input.externalRef || undefined
|
|
99
|
-
if (input.status === 'in-progress') {
|
|
100
|
-
optimisticPlan.status = 'executing'
|
|
101
|
-
optimisticPlan.currentTaskId = targetTask.id
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
yield {
|
|
105
|
-
action: 'task-status-updated',
|
|
106
|
-
message: `Updating task "${targetTask.title}".`,
|
|
107
|
-
changedTaskId: targetTask.id,
|
|
108
|
-
plan: optimisticPlan,
|
|
109
|
-
hasPlan: true,
|
|
110
|
-
status: optimisticPlan.status,
|
|
111
|
-
} satisfies ExecutionPlanToolResultData
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const result = await executionPlanService.setTaskStatus({
|
|
82
|
+
'Submit the result for the currently running execution node. The executor validates outputs, artifacts, and completion checks before advancing the run.',
|
|
83
|
+
inputSchema: SubmitExecutionNodeResultArgsSchema,
|
|
84
|
+
execute: async (input) => {
|
|
85
|
+
const result = await executionPlanService.submitNodeResult({
|
|
116
86
|
workstreamId: params.workstreamId,
|
|
117
87
|
emittedBy: params.agentId,
|
|
118
88
|
input,
|
|
@@ -123,7 +93,7 @@ export function createSetExecutionTaskStatusTool(params: {
|
|
|
123
93
|
toModelOutput: ({ output }) => {
|
|
124
94
|
const result = getLatestExecutionPlanToolResult(output)
|
|
125
95
|
const summary = result?.message?.trim()
|
|
126
|
-
return { type: 'text', value: summary && summary.length > 0 ? summary : 'Execution
|
|
96
|
+
return { type: 'text', value: summary && summary.length > 0 ? summary : 'Execution node result submitted.' }
|
|
127
97
|
},
|
|
128
98
|
})
|
|
129
99
|
}
|
|
@@ -131,27 +101,31 @@ export function createSetExecutionTaskStatusTool(params: {
|
|
|
131
101
|
export function createGetActiveExecutionPlanTool(params: { workstreamId: RecordIdRef }) {
|
|
132
102
|
return tool({
|
|
133
103
|
description:
|
|
134
|
-
'Load the active execution
|
|
104
|
+
'Load the active execution run for this workstream, including graph state, node contracts, recent events, approvals, artifacts, and checkpoints when requested.',
|
|
135
105
|
inputSchema: GetActiveExecutionPlanArgsSchema,
|
|
136
106
|
execute: async (input) =>
|
|
137
107
|
await executionPlanService.getActivePlanToolResult({
|
|
138
108
|
workstreamId: params.workstreamId,
|
|
139
109
|
includeEvents: input.includeEvents,
|
|
110
|
+
includeArtifacts: input.includeArtifacts,
|
|
111
|
+
includeApprovals: input.includeApprovals,
|
|
112
|
+
includeCheckpoints: input.includeCheckpoints,
|
|
113
|
+
includeValidationIssues: input.includeValidationIssues,
|
|
140
114
|
}),
|
|
141
115
|
})
|
|
142
116
|
}
|
|
143
117
|
|
|
144
|
-
export function
|
|
118
|
+
export function createResumeExecutionPlanRunTool(params: {
|
|
145
119
|
workstreamId: RecordIdRef
|
|
146
120
|
agentId: string
|
|
147
121
|
onPlanChanged?: () => void
|
|
148
122
|
}) {
|
|
149
123
|
return tool({
|
|
150
124
|
description:
|
|
151
|
-
'
|
|
152
|
-
inputSchema:
|
|
125
|
+
'Resume the active execution run from its latest checkpoint after interruption. The executor reconciles state before re-activating nodes.',
|
|
126
|
+
inputSchema: ResumeExecutionPlanRunArgsSchema,
|
|
153
127
|
execute: async (input) => {
|
|
154
|
-
const result = await executionPlanService.
|
|
128
|
+
const result = await executionPlanService.resumeRun({
|
|
155
129
|
workstreamId: params.workstreamId,
|
|
156
130
|
emittedBy: params.agentId,
|
|
157
131
|
input,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import Firecrawl from '@mendable/firecrawl-js'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
4
4
|
|
|
5
5
|
let _firecrawlClient: Firecrawl | undefined
|
|
6
6
|
|
|
7
7
|
export function getFirecrawlClient(): Firecrawl {
|
|
8
8
|
if (!_firecrawlClient) {
|
|
9
|
-
_firecrawlClient = new Firecrawl({ apiKey:
|
|
9
|
+
_firecrawlClient = new Firecrawl({ apiKey: getRuntimeConfig().firecrawl.apiKey })
|
|
10
10
|
}
|
|
11
11
|
return _firecrawlClient
|
|
12
12
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './execution-plan.tool'
|
|
2
|
+
export * from './fetch-webpage.tool'
|
|
3
|
+
export * from './log-hello-world.tool'
|
|
4
|
+
export * from './memory-block.tool'
|
|
5
|
+
export * from './read-file-parts.tool'
|
|
6
|
+
export * from './remember-memory.tool'
|
|
7
|
+
export * from './research-topic.tool'
|
|
8
|
+
export * from './search-tools'
|
|
9
|
+
export * from './search-web.tool'
|
|
10
|
+
export * from './team-think.tool'
|
|
11
|
+
export * from './tool-contract'
|
|
12
|
+
export * from './user-questions.tool'
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
}
|