@lota-sdk/core 0.2.3 → 0.3.0
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 +2 -2
- package/infrastructure/schema/00_thread.surql +75 -0
- package/infrastructure/schema/02_execution_plan.surql +10 -11
- package/infrastructure/schema/10_autonomous_job.surql +3 -3
- package/package.json +2 -2
- package/src/ai/definitions.ts +1 -1
- package/src/config/agent-defaults.ts +5 -5
- package/src/config/index.ts +1 -1
- package/src/config/thread-defaults.ts +72 -0
- package/src/create-runtime.ts +89 -93
- package/src/db/tables.ts +3 -3
- package/src/db/{workstream-message-row.ts → thread-message-row.ts} +3 -3
- package/src/queues/context-compaction.queue.ts +6 -6
- package/src/queues/plan-agent-heartbeat.queue.ts +3 -3
- package/src/queues/post-chat-memory.queue.ts +1 -1
- package/src/queues/title-generation.queue.ts +10 -13
- package/src/redis/index.ts +1 -1
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/agent-identity-overrides.ts +1 -1
- package/src/runtime/agent-runtime-policy.ts +19 -21
- package/src/runtime/chat-request-routing.ts +1 -1
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction.ts +1 -1
- package/src/runtime/execution-plan.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src/runtime/memory-digest-policy.ts +1 -1
- package/src/runtime/plugin-types.ts +1 -1
- package/src/runtime/post-turn-side-effects.ts +35 -35
- package/src/runtime/runtime-config.ts +12 -12
- package/src/runtime/runtime-extensions.ts +11 -11
- package/src/runtime/social-chat-agent-runner.ts +3 -3
- package/src/runtime/social-chat-history.ts +1 -1
- package/src/runtime/social-chat.ts +6 -6
- package/src/runtime/team-consultation-orchestrator.ts +1 -1
- package/src/runtime/{workstream-chat-helpers.ts → thread-chat-helpers.ts} +7 -7
- package/src/runtime/{workstream-plan-turn.ts → thread-plan-turn.ts} +11 -17
- package/src/runtime/{workstream-turn-context.ts → thread-turn-context.ts} +10 -10
- package/src/services/agent-activity.service.ts +39 -44
- package/src/services/agent-executor.service.ts +17 -19
- package/src/services/attachment.service.ts +4 -8
- package/src/services/autonomous-job.service.ts +29 -28
- package/src/services/context-compaction.service.ts +19 -29
- package/src/services/execution-plan.service.ts +58 -70
- package/src/services/global-orchestrator.service.ts +5 -5
- package/src/services/index.ts +6 -6
- package/src/services/memory.service.ts +1 -1
- package/src/services/monitoring-window.service.ts +2 -2
- package/src/services/mutating-approval.service.ts +7 -10
- package/src/services/node-workspace.service.ts +8 -7
- package/src/services/notification.service.ts +1 -1
- package/src/services/organization.service.ts +9 -9
- package/src/services/ownership-dispatcher.service.ts +13 -19
- package/src/services/plan-agent-heartbeat.service.ts +13 -13
- package/src/services/plan-agent-query.service.ts +7 -7
- package/src/services/plan-artifact.service.ts +1 -2
- package/src/services/plan-coordination.service.ts +4 -4
- package/src/services/plan-cycle.service.ts +7 -7
- package/src/services/plan-deadline.service.ts +4 -4
- package/src/services/plan-event-delivery.service.ts +8 -12
- package/src/services/plan-executor.service.ts +16 -37
- package/src/services/plan-run-data.ts +27 -8
- package/src/services/plan-run.service.ts +7 -9
- package/src/services/plan-scheduler.service.ts +4 -4
- package/src/services/plan-template.service.ts +2 -2
- package/src/services/plan-validator.service.ts +0 -11
- package/src/services/plugin-executor.service.ts +1 -1
- package/src/services/queue-job.service.ts +1 -1
- package/src/services/recent-activity-title.service.ts +1 -1
- package/src/services/recent-activity.service.ts +4 -4
- package/src/services/system-executor.service.ts +2 -2
- package/src/services/{workstream-message.service.ts → thread-message.service.ts} +72 -76
- package/src/services/thread-plan-registry.service.ts +22 -0
- package/src/services/thread-title.service.ts +39 -0
- package/src/services/{workstream-turn-preparation.service.ts → thread-turn-preparation.service.ts} +131 -143
- package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
- package/src/services/thread.service.ts +707 -0
- package/src/services/thread.types.ts +17 -0
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/system-agents/index.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/researcher.agent.ts +3 -3
- package/src/system-agents/{workstream-router.agent.ts → thread-router.agent.ts} +21 -21
- package/src/system-agents/title-generator.agent.ts +8 -8
- package/src/tools/execution-plan.tool.ts +39 -40
- package/src/tools/memory-block.tool.ts +4 -4
- package/src/tools/research-topic.tool.ts +1 -0
- package/src/tools/search-web.tool.ts +1 -1
- package/src/tools/search.tool.ts +4 -4
- package/src/tools/team-think.tool.ts +9 -9
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +43 -43
- package/src/workers/skill-extraction.runner.ts +9 -13
- package/src/workers/utils/{workstream-message-query.ts → thread-message-query.ts} +21 -21
- package/infrastructure/schema/00_workstream.surql +0 -64
- package/src/config/workstream-defaults.ts +0 -72
- package/src/services/workstream-plan-registry.service.ts +0 -22
- package/src/services/workstream-title.service.ts +0 -42
- package/src/services/workstream.service.ts +0 -803
- package/src/services/workstream.types.ts +0 -17
- /package/src/services/{workstream-constants.ts → thread-constants.ts} +0 -0
|
@@ -16,8 +16,8 @@ import type {
|
|
|
16
16
|
import { agentRoster } from '../config/agent-defaults'
|
|
17
17
|
import { serverLogger } from '../config/logger'
|
|
18
18
|
import { executionPlanService } from './execution-plan.service'
|
|
19
|
-
import {
|
|
20
|
-
import type {
|
|
19
|
+
import { threadService } from './thread.service'
|
|
20
|
+
import type { NormalizedThread } from './thread.types'
|
|
21
21
|
|
|
22
22
|
const BOARD_COLUMN_ORDER = ['ready', 'running', 'awaiting-human', 'completed', 'blocked', 'failed'] as const
|
|
23
23
|
type BoardColumnStatus = (typeof BOARD_COLUMN_ORDER)[number]
|
|
@@ -33,12 +33,12 @@ const COLUMN_LABELS: Record<BoardColumnStatus, string> = {
|
|
|
33
33
|
|
|
34
34
|
type ActivePlanEntry = {
|
|
35
35
|
plan: SerializableExecutionPlan
|
|
36
|
-
|
|
36
|
+
thread: Pick<NormalizedThread, 'id' | 'title' | 'isRunning' | 'updatedAt'>
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
type AgentActivityDeps = {
|
|
40
|
-
executionPlanService: Pick<typeof executionPlanService, '
|
|
41
|
-
|
|
40
|
+
executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForThread'>
|
|
41
|
+
threadService: Pick<typeof threadService, 'listThreads'>
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function normalizeCardStatus(status: string): BoardColumnStatus {
|
|
@@ -97,8 +97,8 @@ function incrementCounts(counts: AgentActivityCounts, rawStatus: string): void {
|
|
|
97
97
|
export function planNodeToCard(
|
|
98
98
|
node: SerializablePlanNode,
|
|
99
99
|
plan: SerializableExecutionPlan,
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
threadId: string,
|
|
101
|
+
threadTitle: string,
|
|
102
102
|
): PlanNodeCard {
|
|
103
103
|
const approval = plan.approvals.find((candidate) => candidate.nodeId === node.id && candidate.status === 'pending')
|
|
104
104
|
|
|
@@ -111,8 +111,8 @@ export function planNodeToCard(
|
|
|
111
111
|
ownerRef: node.owner.ref,
|
|
112
112
|
planRunId: plan.runId,
|
|
113
113
|
planTitle: plan.title,
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
threadId,
|
|
115
|
+
threadTitle,
|
|
116
116
|
nodeType: node.type,
|
|
117
117
|
artifactCount: plan.artifacts.filter((artifact) => artifact.nodeId === node.id).length,
|
|
118
118
|
hasApproval: Boolean(approval),
|
|
@@ -129,11 +129,11 @@ export function planNodeToCard(
|
|
|
129
129
|
function buildPlanViewNode(
|
|
130
130
|
node: SerializablePlanNode,
|
|
131
131
|
plan: SerializableExecutionPlan,
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
threadId: string,
|
|
133
|
+
threadTitle: string,
|
|
134
134
|
): PlanViewNode {
|
|
135
135
|
return {
|
|
136
|
-
...planNodeToCard(node, plan,
|
|
136
|
+
...planNodeToCard(node, plan, threadId, threadTitle),
|
|
137
137
|
deliverableNames: node.deliverables.map((deliverable) => deliverable.name),
|
|
138
138
|
upstreamNodeIds: node.upstreamNodeIds,
|
|
139
139
|
downstreamNodeIds: node.downstreamNodeIds,
|
|
@@ -141,12 +141,12 @@ function buildPlanViewNode(
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export class AgentActivityService {
|
|
144
|
-
constructor(private readonly deps: AgentActivityDeps = { executionPlanService,
|
|
144
|
+
constructor(private readonly deps: AgentActivityDeps = { executionPlanService, threadService }) {}
|
|
145
145
|
|
|
146
146
|
async getBoard(userRef: string, orgRef: string): Promise<PlanBoardResponse> {
|
|
147
147
|
const activePlans = await this.getAllActivePlans(userRef, orgRef)
|
|
148
|
-
const cards = activePlans.flatMap(({ plan,
|
|
149
|
-
plan.nodes.map((node) => planNodeToCard(node, plan,
|
|
148
|
+
const cards = activePlans.flatMap(({ plan, thread }) =>
|
|
149
|
+
plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)),
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
|
|
@@ -171,7 +171,7 @@ export class AgentActivityService {
|
|
|
171
171
|
const match = activePlans.find(({ plan }) => plan.runId === planRunId)
|
|
172
172
|
if (!match) return null
|
|
173
173
|
|
|
174
|
-
const { plan,
|
|
174
|
+
const { plan, thread } = match
|
|
175
175
|
return {
|
|
176
176
|
planRunId: plan.runId,
|
|
177
177
|
title: plan.title,
|
|
@@ -179,7 +179,7 @@ export class AgentActivityService {
|
|
|
179
179
|
status: plan.status,
|
|
180
180
|
leadAgentId: plan.leadAgentId,
|
|
181
181
|
progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
|
|
182
|
-
nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan,
|
|
182
|
+
nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
|
|
183
183
|
edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -188,13 +188,13 @@ export class AgentActivityService {
|
|
|
188
188
|
const activePlans = await this.getAllActivePlans(userRef, orgRef)
|
|
189
189
|
const tasks: PlanNodeCard[] = []
|
|
190
190
|
|
|
191
|
-
for (const { plan,
|
|
191
|
+
for (const { plan, thread } of activePlans) {
|
|
192
192
|
for (const node of plan.nodes) {
|
|
193
193
|
const humanOwned = node.owner.executorType === 'user'
|
|
194
194
|
const awaitingHuman = node.status === 'awaiting-human'
|
|
195
195
|
if (!humanOwned && !awaitingHuman) continue
|
|
196
196
|
|
|
197
|
-
tasks.push(planNodeToCard(node, plan,
|
|
197
|
+
tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -209,7 +209,7 @@ export class AgentActivityService {
|
|
|
209
209
|
activityByAgent.set(agentId, this.createEmptyEntry(agentId))
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
for (const { plan,
|
|
212
|
+
for (const { plan, thread } of activePlans) {
|
|
213
213
|
const involvedAgents = new Set<string>()
|
|
214
214
|
|
|
215
215
|
for (const node of plan.nodes) {
|
|
@@ -221,7 +221,7 @@ export class AgentActivityService {
|
|
|
221
221
|
incrementCounts(entry.counts, node.status)
|
|
222
222
|
|
|
223
223
|
if (!isCompletedStatus(node.status)) {
|
|
224
|
-
entry.tasks.push(planNodeToCard(node, plan,
|
|
224
|
+
entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -234,15 +234,15 @@ export class AgentActivityService {
|
|
|
234
234
|
for (const agentId of involvedAgents) {
|
|
235
235
|
const entry = this.ensureEntry(activityByAgent, agentId)
|
|
236
236
|
this.ensureProjectEntry(entry.projects, {
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
threadId: thread.id,
|
|
238
|
+
threadTitle: thread.title,
|
|
239
239
|
planRunId: plan.runId,
|
|
240
240
|
planTitle: plan.title,
|
|
241
241
|
status: plan.status,
|
|
242
242
|
})
|
|
243
243
|
|
|
244
|
-
entry.isRunning = entry.isRunning ||
|
|
245
|
-
entry.lastActiveAt = maxIsoDate(entry.lastActiveAt,
|
|
244
|
+
entry.isRunning = entry.isRunning || thread.isRunning
|
|
245
|
+
entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -265,7 +265,7 @@ export class AgentActivityService {
|
|
|
265
265
|
pendingApprovalCount: userTasks.pendingApprovalCount,
|
|
266
266
|
awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
|
|
267
267
|
lastActiveAt: activePlans.reduce<string | null>(
|
|
268
|
-
(latest, entry) => maxIsoDate(latest, entry.
|
|
268
|
+
(latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
|
|
269
269
|
null,
|
|
270
270
|
),
|
|
271
271
|
},
|
|
@@ -274,14 +274,14 @@ export class AgentActivityService {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
async getAllActivePlans(userRef: string, orgRef: string): Promise<ActivePlanEntry[]> {
|
|
277
|
-
const
|
|
277
|
+
const threads = await this.listRelevantThreads(userRef, orgRef)
|
|
278
278
|
const planResults = await Promise.all(
|
|
279
|
-
|
|
279
|
+
threads.map(async (thread) => {
|
|
280
280
|
try {
|
|
281
|
-
const plans = await this.deps.executionPlanService.
|
|
282
|
-
return plans.map((plan) => ({ plan,
|
|
281
|
+
const plans = await this.deps.executionPlanService.getActivePlansForThread(thread.id)
|
|
282
|
+
return plans.map((plan) => ({ plan, thread }))
|
|
283
283
|
} catch (error) {
|
|
284
|
-
serverLogger.error`Failed to load active plans for
|
|
284
|
+
serverLogger.error`Failed to load active plans for thread ${thread.id}: ${error}`
|
|
285
285
|
return []
|
|
286
286
|
}
|
|
287
287
|
}),
|
|
@@ -290,26 +290,21 @@ export class AgentActivityService {
|
|
|
290
290
|
return planResults.flat()
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
private async
|
|
293
|
+
private async listRelevantThreads(userRef: string, orgRef: string): Promise<NormalizedThread[]> {
|
|
294
294
|
const [direct, core, group] = await Promise.all([
|
|
295
|
-
this.deps.
|
|
296
|
-
this.deps.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
includeArchived: false,
|
|
300
|
-
}),
|
|
301
|
-
this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
|
|
302
|
-
mode: 'group',
|
|
303
|
-
core: false,
|
|
295
|
+
this.deps.threadService.listThreads(userRef, orgRef, { type: 'default', includeArchived: false }),
|
|
296
|
+
this.deps.threadService.listThreads(userRef, orgRef, { type: 'thread', includeArchived: false }),
|
|
297
|
+
this.deps.threadService.listThreads(userRef, orgRef, {
|
|
298
|
+
type: 'group',
|
|
304
299
|
includeArchived: false,
|
|
305
300
|
take: 500,
|
|
306
301
|
page: 1,
|
|
307
302
|
}),
|
|
308
303
|
])
|
|
309
304
|
|
|
310
|
-
const deduped = new Map<string,
|
|
311
|
-
for (const
|
|
312
|
-
deduped.set(
|
|
305
|
+
const deduped = new Map<string, NormalizedThread>()
|
|
306
|
+
for (const thread of [...direct.threads, ...core.threads, ...group.threads]) {
|
|
307
|
+
deduped.set(thread.id, thread)
|
|
313
308
|
}
|
|
314
309
|
|
|
315
310
|
return [...deduped.values()]
|
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
25
25
|
import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
|
|
26
26
|
import { getTurnHooks } from '../runtime/runtime-extensions'
|
|
27
|
-
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/
|
|
27
|
+
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
|
|
28
28
|
import { nodeWorkspaceService } from './node-workspace.service'
|
|
29
29
|
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
30
|
-
import {
|
|
30
|
+
import { ThreadSchema } from './thread.types'
|
|
31
31
|
import { writeIntentValidatorService } from './write-intent-validator.service'
|
|
32
32
|
|
|
33
33
|
function applyToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpec): ToolSet {
|
|
@@ -108,20 +108,20 @@ class AgentExecutorService {
|
|
|
108
108
|
throw new Error(`Agent executor "${agentId}" is not registered.`)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const
|
|
112
|
-
TABLES.
|
|
113
|
-
{ id: ensureRecordId(params.context.
|
|
114
|
-
|
|
111
|
+
const thread = await databaseService.findOne(
|
|
112
|
+
TABLES.THREAD,
|
|
113
|
+
{ id: ensureRecordId(params.context.threadId, TABLES.THREAD) },
|
|
114
|
+
ThreadSchema,
|
|
115
115
|
)
|
|
116
|
-
if (!
|
|
117
|
-
throw new Error(`
|
|
116
|
+
if (!thread) {
|
|
117
|
+
throw new Error(`Thread ${params.context.threadId} not found for dispatched execution.`)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
|
|
121
|
-
const
|
|
122
|
-
const userRefSource = params.context.userId ??
|
|
121
|
+
const threadRef = ensureRecordId(params.context.threadId, TABLES.THREAD)
|
|
122
|
+
const userRefSource = params.context.userId ?? thread.userId
|
|
123
123
|
if (!userRefSource) {
|
|
124
|
-
throw new Error(`
|
|
124
|
+
throw new Error(`Thread ${params.context.threadId} is missing a user context for dispatched execution.`)
|
|
125
125
|
}
|
|
126
126
|
const userRef = ensureRecordId(userRefSource, TABLES.USER)
|
|
127
127
|
const userName = params.context.userName ?? 'User'
|
|
@@ -147,7 +147,7 @@ class AgentExecutorService {
|
|
|
147
147
|
|
|
148
148
|
const mode = params.executionMode ?? 'linear'
|
|
149
149
|
|
|
150
|
-
const dispatchMode =
|
|
150
|
+
const dispatchMode = thread.type === 'group' ? 'fixedThreadMode' : 'direct'
|
|
151
151
|
const dispatchInstructionSections = [
|
|
152
152
|
buildOwnershipDispatchContextSection({
|
|
153
153
|
node: params.nodeSpec,
|
|
@@ -161,8 +161,8 @@ class AgentExecutorService {
|
|
|
161
161
|
await turnHooks.resolveAgent?.({
|
|
162
162
|
agentId,
|
|
163
163
|
mode: dispatchMode,
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
thread,
|
|
165
|
+
threadRef,
|
|
166
166
|
orgRef: organizationRef,
|
|
167
167
|
userRef,
|
|
168
168
|
userName,
|
|
@@ -176,7 +176,7 @@ class AgentExecutorService {
|
|
|
176
176
|
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
177
177
|
const runtimeConfig = getAgentRuntimeConfig({
|
|
178
178
|
agentId: resolvedAgentId,
|
|
179
|
-
|
|
179
|
+
threadType: thread.type,
|
|
180
180
|
mode: dispatchMode,
|
|
181
181
|
onboardingActive: false,
|
|
182
182
|
linearInstalled: Boolean(linearInstallation),
|
|
@@ -192,9 +192,9 @@ class AgentExecutorService {
|
|
|
192
192
|
orgId: organizationRef,
|
|
193
193
|
userId: userRef,
|
|
194
194
|
userName,
|
|
195
|
-
|
|
195
|
+
threadId: threadRef,
|
|
196
196
|
orgIdString: params.context.organizationId,
|
|
197
|
-
|
|
197
|
+
threadType: thread.type,
|
|
198
198
|
mode: dispatchMode,
|
|
199
199
|
linearInstalled: Boolean(linearInstallation),
|
|
200
200
|
onboardingActive: false,
|
|
@@ -302,7 +302,6 @@ class AgentExecutorService {
|
|
|
302
302
|
structuredOutput: finalResult.structuredOutput,
|
|
303
303
|
artifacts: finalResult.artifacts,
|
|
304
304
|
notes: 'Execution incomplete: missing required deliverables or validation failures.',
|
|
305
|
-
quality: 'partial',
|
|
306
305
|
}
|
|
307
306
|
nodeWorkspaceService.cleanup(workspace)
|
|
308
307
|
return result
|
|
@@ -312,7 +311,6 @@ class AgentExecutorService {
|
|
|
312
311
|
structuredOutput: finalResult.structuredOutput,
|
|
313
312
|
artifacts: finalResult.artifacts,
|
|
314
313
|
notes: finalResult.notes,
|
|
315
|
-
quality: finalResult.quality,
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
nodeWorkspaceService.cleanup(workspace)
|
|
@@ -2,7 +2,7 @@ import type { RecordIdRef } from '../db/record-id'
|
|
|
2
2
|
import { recordIdToString } from '../db/record-id'
|
|
3
3
|
import { TABLES } from '../db/tables'
|
|
4
4
|
import { attachmentStorageService } from '../storage/attachment-storage.service'
|
|
5
|
-
import type {
|
|
5
|
+
import type { UploadedThreadAttachment as SdkUploadedThreadAttachment } from '../storage/attachment-storage.service'
|
|
6
6
|
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
|
|
7
7
|
|
|
8
8
|
export type ReadableUploadMetadata = SdkReadableUploadMetadata
|
|
@@ -134,7 +134,7 @@ class AttachmentService {
|
|
|
134
134
|
return attachmentStorageService.uploadOrganizationDocument({ file, orgId: toOrgId(orgId), namespace, relativePath })
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
async
|
|
137
|
+
async uploadThreadAttachment({
|
|
138
138
|
file,
|
|
139
139
|
orgId,
|
|
140
140
|
userId,
|
|
@@ -142,12 +142,8 @@ class AttachmentService {
|
|
|
142
142
|
file: File
|
|
143
143
|
orgId: RecordIdRef
|
|
144
144
|
userId: RecordIdRef
|
|
145
|
-
}): Promise<
|
|
146
|
-
return attachmentStorageService.
|
|
147
|
-
file,
|
|
148
|
-
orgId: toOrgId(orgId),
|
|
149
|
-
userId: toUserId(userId),
|
|
150
|
-
})
|
|
145
|
+
}): Promise<SdkUploadedThreadAttachment> {
|
|
146
|
+
return attachmentStorageService.uploadThreadAttachment({ file, orgId: toOrgId(orgId), userId: toUserId(userId) })
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
|
|
@@ -30,15 +30,15 @@ import type { RecordIdInput } from '../db/record-id'
|
|
|
30
30
|
import { databaseService } from '../db/service'
|
|
31
31
|
import { TABLES } from '../db/tables'
|
|
32
32
|
import type { AutonomousJobQueuePayload } from '../queues/autonomous-job.queue'
|
|
33
|
-
import { extractMessageText } from '../runtime/
|
|
33
|
+
import { extractMessageText } from '../runtime/thread-chat-helpers'
|
|
34
34
|
import { buildAutonomousAtJobId, encodeBullmqId } from '../utils/autonomous-job-ids'
|
|
35
35
|
import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
|
|
36
36
|
import { compactRecord, compactWhitespace, stringifyUnknown, truncateText } from '../utils/string'
|
|
37
37
|
import { executionPlanService } from './execution-plan.service'
|
|
38
38
|
import { getNotificationService } from './notification.service'
|
|
39
39
|
import { queueJobService } from './queue-job.service'
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
40
|
+
import { runThreadTurnInBackground } from './thread-turn'
|
|
41
|
+
import { threadService } from './thread.service'
|
|
42
42
|
|
|
43
43
|
const AUTONOMOUS_JOB_QUEUE_NAME = 'autonomous-job'
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ const AutonomousJobRowSchema = z.object({
|
|
|
51
51
|
organizationId: recordIdSchema,
|
|
52
52
|
ownerUserId: recordIdSchema,
|
|
53
53
|
ownerUserName: z.string().optional(),
|
|
54
|
-
|
|
54
|
+
threadId: recordIdSchema,
|
|
55
55
|
agentId: z.string(),
|
|
56
56
|
title: z.string(),
|
|
57
57
|
prompt: z.string(),
|
|
@@ -72,7 +72,7 @@ const AutonomousJobRowSchema = z.object({
|
|
|
72
72
|
const AutonomousJobRunRowSchema = z.object({
|
|
73
73
|
id: recordIdSchema,
|
|
74
74
|
autonomousJobId: recordIdSchema,
|
|
75
|
-
|
|
75
|
+
threadId: recordIdSchema,
|
|
76
76
|
queueJobId: recordIdSchema.optional(),
|
|
77
77
|
status: AutonomousJobRunStatusSchema,
|
|
78
78
|
inputMessageId: z.string().optional(),
|
|
@@ -111,7 +111,7 @@ class AutonomousJobService {
|
|
|
111
111
|
organizationId: recordIdToString(row.organizationId, TABLES.ORGANIZATION),
|
|
112
112
|
ownerUserId: recordIdToString(row.ownerUserId, TABLES.USER),
|
|
113
113
|
ownerUserName: row.ownerUserName,
|
|
114
|
-
|
|
114
|
+
threadId: recordIdToString(row.threadId, TABLES.THREAD),
|
|
115
115
|
agentId: row.agentId,
|
|
116
116
|
title: row.title,
|
|
117
117
|
prompt: row.prompt,
|
|
@@ -134,7 +134,7 @@ class AutonomousJobService {
|
|
|
134
134
|
return AutonomousJobRunSchema.parse({
|
|
135
135
|
id: recordIdToString(row.id, TABLES.AUTONOMOUS_JOB_RUN),
|
|
136
136
|
autonomousJobId: recordIdToString(row.autonomousJobId, TABLES.AUTONOMOUS_JOB),
|
|
137
|
-
|
|
137
|
+
threadId: recordIdToString(row.threadId, TABLES.THREAD),
|
|
138
138
|
queueJobId: row.queueJobId ? recordIdToString(row.queueJobId, TABLES.QUEUE_JOB) : undefined,
|
|
139
139
|
status: row.status,
|
|
140
140
|
inputMessageId: row.inputMessageId,
|
|
@@ -175,7 +175,7 @@ class AutonomousJobService {
|
|
|
175
175
|
kind: 'notify',
|
|
176
176
|
params: {
|
|
177
177
|
organizationId: string
|
|
178
|
-
|
|
178
|
+
threadId: string
|
|
179
179
|
title: string
|
|
180
180
|
body: string
|
|
181
181
|
severity: 'info' | 'warning'
|
|
@@ -204,7 +204,7 @@ class AutonomousJobService {
|
|
|
204
204
|
|
|
205
205
|
private async createRunRow(params: {
|
|
206
206
|
autonomousJobId: RecordIdInput
|
|
207
|
-
|
|
207
|
+
threadId: RecordIdInput
|
|
208
208
|
queueJobId?: RecordIdInput
|
|
209
209
|
status?: AutonomousJobRunStatus
|
|
210
210
|
}): Promise<AutonomousJobRunRow> {
|
|
@@ -214,7 +214,7 @@ class AutonomousJobService {
|
|
|
214
214
|
runId,
|
|
215
215
|
compactRecord({
|
|
216
216
|
autonomousJobId: ensureRecordId(params.autonomousJobId, TABLES.AUTONOMOUS_JOB),
|
|
217
|
-
|
|
217
|
+
threadId: ensureRecordId(params.threadId, TABLES.THREAD),
|
|
218
218
|
queueJobId: params.queueJobId ? ensureRecordId(params.queueJobId, TABLES.QUEUE_JOB) : undefined,
|
|
219
219
|
status: params.status ?? 'queued',
|
|
220
220
|
assistantMessageIds: [],
|
|
@@ -293,8 +293,8 @@ class AutonomousJobService {
|
|
|
293
293
|
if (row.schedule.kind === 'at') {
|
|
294
294
|
const queuedRun = options.reusePendingAtRun
|
|
295
295
|
? ((await this.findRecoverableRunRow(row.id)) ??
|
|
296
|
-
(await this.createRunRow({ autonomousJobId: row.id,
|
|
297
|
-
: await this.createRunRow({ autonomousJobId: row.id,
|
|
296
|
+
(await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })))
|
|
297
|
+
: await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
|
|
298
298
|
const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
|
|
299
299
|
const enqueueResult = await enqueueAutonomousJobRun({
|
|
300
300
|
payload: {
|
|
@@ -334,10 +334,11 @@ class AutonomousJobService {
|
|
|
334
334
|
const parsed = CreateAutonomousJobInputSchema.parse(input)
|
|
335
335
|
const organizationId = ensureRecordId(parsed.organizationId, TABLES.ORGANIZATION)
|
|
336
336
|
const ownerUserId = ensureRecordId(parsed.ownerUserId, TABLES.USER)
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
const thread = await threadService.createThread({
|
|
338
|
+
userId: ownerUserId,
|
|
339
|
+
organizationId,
|
|
340
|
+
type: 'group',
|
|
341
|
+
title: parsed.threadTitle ?? parsed.title,
|
|
341
342
|
})
|
|
342
343
|
const jobId = new RecordId(TABLES.AUTONOMOUS_JOB, Bun.randomUUIDv7())
|
|
343
344
|
const nextRunAt = this.computeNextRunAt(parsed.schedule)
|
|
@@ -348,7 +349,7 @@ class AutonomousJobService {
|
|
|
348
349
|
organizationId,
|
|
349
350
|
ownerUserId,
|
|
350
351
|
ownerUserName: parsed.ownerUserName,
|
|
351
|
-
|
|
352
|
+
threadId: ensureRecordId(thread.id, TABLES.THREAD),
|
|
352
353
|
agentId: parsed.agentId,
|
|
353
354
|
title: parsed.title,
|
|
354
355
|
prompt: parsed.prompt,
|
|
@@ -409,7 +410,7 @@ class AutonomousJobService {
|
|
|
409
410
|
await this.unscheduleRow(existing)
|
|
410
411
|
|
|
411
412
|
if (parsed.title && compactWhitespace(parsed.title) !== compactWhitespace(existing.title)) {
|
|
412
|
-
await
|
|
413
|
+
await threadService.updateTitle(existing.threadId, parsed.title)
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
const nextRunAt = this.computeNextRunAt(parsed.schedule ?? existing.schedule)
|
|
@@ -465,7 +466,7 @@ class AutonomousJobService {
|
|
|
465
466
|
|
|
466
467
|
async runNow(jobId: RecordIdInput): Promise<AutonomousJobRun> {
|
|
467
468
|
const row = await this.getRow(jobId)
|
|
468
|
-
const queuedRun = await this.createRunRow({ autonomousJobId: row.id,
|
|
469
|
+
const queuedRun = await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
|
|
469
470
|
const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
|
|
470
471
|
const enqueueResult = await enqueueAutonomousJobRun({
|
|
471
472
|
payload: {
|
|
@@ -501,7 +502,7 @@ class AutonomousJobService {
|
|
|
501
502
|
async delete(jobId: RecordIdInput): Promise<AutonomousJob> {
|
|
502
503
|
const row = await this.getRow(jobId)
|
|
503
504
|
const cancelled = await this.cancel(row.id)
|
|
504
|
-
await
|
|
505
|
+
await threadService.updateStatus(row.threadId, 'archived')
|
|
505
506
|
return cancelled
|
|
506
507
|
}
|
|
507
508
|
|
|
@@ -526,7 +527,7 @@ class AutonomousJobService {
|
|
|
526
527
|
? await this.getRunRow(job.data.autonomousJobRunId)
|
|
527
528
|
: await this.createRunRow({
|
|
528
529
|
autonomousJobId: autonomousJobRow.id,
|
|
529
|
-
|
|
530
|
+
threadId: autonomousJobRow.threadId,
|
|
530
531
|
queueJobId,
|
|
531
532
|
status: 'queued',
|
|
532
533
|
})
|
|
@@ -546,18 +547,18 @@ class AutonomousJobService {
|
|
|
546
547
|
)) ?? runRow
|
|
547
548
|
|
|
548
549
|
try {
|
|
549
|
-
const
|
|
550
|
+
const thread = await threadService.getThread(autonomousJobRow.threadId)
|
|
550
551
|
const inputMessage = this.buildSyntheticUserMessage(autonomousJobRow.prompt)
|
|
551
|
-
const turnResult = await
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
const turnResult = await runThreadTurnInBackground({
|
|
553
|
+
thread,
|
|
554
|
+
threadRef: ensureRecordId(autonomousJobRow.threadId, TABLES.THREAD),
|
|
554
555
|
orgRef: ensureRecordId(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
|
|
555
556
|
userRef: ensureRecordId(autonomousJobRow.ownerUserId, TABLES.USER),
|
|
556
557
|
userName: autonomousJobRow.ownerUserName,
|
|
557
558
|
agentIdOverride: autonomousJobRow.agentId,
|
|
558
559
|
inputMessage,
|
|
559
560
|
})
|
|
560
|
-
const activePlan = await executionPlanService.
|
|
561
|
+
const activePlan = await executionPlanService.getActivePlanForThread(autonomousJobRow.threadId)
|
|
561
562
|
const runStatus: AutonomousJobRunStatus = activePlan?.status === 'awaiting-human' ? 'awaiting-human' : 'completed'
|
|
562
563
|
const summary = truncateText(
|
|
563
564
|
turnResult.assistantMessages
|
|
@@ -611,7 +612,7 @@ class AutonomousJobService {
|
|
|
611
612
|
|
|
612
613
|
await this.maybeNotify('notify', {
|
|
613
614
|
organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
|
|
614
|
-
|
|
615
|
+
threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
|
|
615
616
|
severity: 'info',
|
|
616
617
|
title: `${autonomousJobRow.title} completed`,
|
|
617
618
|
body: summary,
|
|
@@ -664,7 +665,7 @@ class AutonomousJobService {
|
|
|
664
665
|
|
|
665
666
|
await this.maybeNotify('notify', {
|
|
666
667
|
organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
|
|
667
|
-
|
|
668
|
+
threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
|
|
668
669
|
severity: 'warning',
|
|
669
670
|
title: autoPause
|
|
670
671
|
? `${autonomousJobRow.title} paused after repeated failures`
|
|
@@ -7,13 +7,13 @@ import { databaseService } from '../db/service'
|
|
|
7
7
|
import { TABLES } from '../db/tables'
|
|
8
8
|
import { getRedisConnection } from '../redis/connection-accessor'
|
|
9
9
|
import { withRedisLeaseLock } from '../redis/redis-lease-lock'
|
|
10
|
-
import { CONTEXT_WINDOW_TOKENS,
|
|
10
|
+
import { CONTEXT_WINDOW_TOKENS, THREAD_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
|
|
11
11
|
import { contextCompactionRuntime, compactMemoryBlockSummary } from './context-compaction-runtime.singleton'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { threadMessageService } from './thread-message.service'
|
|
13
|
+
import { ThreadSchema } from './thread.types'
|
|
14
14
|
|
|
15
15
|
interface PersistedCompactionMetrics {
|
|
16
|
-
domain: '
|
|
16
|
+
domain: 'thread'
|
|
17
17
|
entityId: string
|
|
18
18
|
inputChars: number
|
|
19
19
|
outputChars: number
|
|
@@ -37,11 +37,8 @@ class ContextCompactionService {
|
|
|
37
37
|
return contextCompactionRuntime.shouldCompactHistory(params)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async
|
|
41
|
-
|
|
42
|
-
contextSize?: number
|
|
43
|
-
}): Promise<{ compacted: boolean }> {
|
|
44
|
-
const entityId = recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
|
|
40
|
+
async compactThreadHistory(params: { threadId: RecordIdRef; contextSize?: number }): Promise<{ compacted: boolean }> {
|
|
41
|
+
const entityId = recordIdToString(params.threadId, TABLES.THREAD)
|
|
45
42
|
|
|
46
43
|
return withRedisLeaseLock(
|
|
47
44
|
{
|
|
@@ -52,24 +49,20 @@ class ContextCompactionService {
|
|
|
52
49
|
label: 'context-compaction',
|
|
53
50
|
},
|
|
54
51
|
async () => {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
WorkstreamSchema,
|
|
59
|
-
)
|
|
60
|
-
if (!workstream) {
|
|
61
|
-
throw new Error(`Workstream not found for compaction: ${entityId}`)
|
|
52
|
+
const thread = await databaseService.findOne(TABLES.THREAD, { id: params.threadId }, ThreadSchema)
|
|
53
|
+
if (!thread) {
|
|
54
|
+
throw new Error(`Thread not found for compaction: ${entityId}`)
|
|
62
55
|
}
|
|
63
56
|
|
|
64
|
-
const liveMessages = await
|
|
65
|
-
params.
|
|
66
|
-
typeof
|
|
57
|
+
const liveMessages = await threadMessageService.listMessagesAfterCursor(
|
|
58
|
+
params.threadId,
|
|
59
|
+
typeof thread.lastCompactedMessageId === 'string' ? thread.lastCompactedMessageId : undefined,
|
|
67
60
|
)
|
|
68
61
|
|
|
69
62
|
const result = await contextCompactionRuntime.compactHistory({
|
|
70
|
-
summaryText: typeof
|
|
63
|
+
summaryText: typeof thread.compactionSummary === 'string' ? thread.compactionSummary : '',
|
|
71
64
|
liveMessages,
|
|
72
|
-
tailMessageCount:
|
|
65
|
+
tailMessageCount: THREAD_RAW_TAIL_MESSAGES,
|
|
73
66
|
contextSize: params.contextSize,
|
|
74
67
|
})
|
|
75
68
|
|
|
@@ -78,21 +71,18 @@ class ContextCompactionService {
|
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
if (result.compactedMessages.length > 0) {
|
|
81
|
-
await
|
|
82
|
-
workstreamId: params.workstreamId,
|
|
83
|
-
messages: result.compactedMessages,
|
|
84
|
-
})
|
|
74
|
+
await threadMessageService.upsertMessages({ threadId: params.threadId, messages: result.compactedMessages })
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
await databaseService.update(
|
|
88
|
-
TABLES.
|
|
89
|
-
params.
|
|
78
|
+
TABLES.THREAD,
|
|
79
|
+
params.threadId,
|
|
90
80
|
{ compactionSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId },
|
|
91
|
-
|
|
81
|
+
ThreadSchema,
|
|
92
82
|
)
|
|
93
83
|
|
|
94
84
|
this.logCompactionMetrics({
|
|
95
|
-
domain: '
|
|
85
|
+
domain: 'thread',
|
|
96
86
|
entityId,
|
|
97
87
|
inputChars: result.inputChars,
|
|
98
88
|
outputChars: result.outputChars,
|