@lota-sdk/core 0.2.3 → 0.3.1
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 +73 -0
- package/infrastructure/schema/02_execution_plan.surql +10 -11
- package/infrastructure/schema/04_runtime_bootstrap.surql +1 -0
- 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 +90 -94
- package/src/db/record-id.ts +21 -21
- package/src/db/service.ts +44 -40
- 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 +24 -21
- 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 +25 -39
- 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} +148 -171
- package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
- package/src/services/thread.service.ts +853 -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} +68 -135
- 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/utils/async.ts +6 -7
- 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
|
@@ -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,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ChatMessage,
|
|
3
|
+
ExecutionPlanQueryArgs,
|
|
3
4
|
ExecutionPlanToolResultData,
|
|
4
|
-
GetActiveExecutionPlanArgs,
|
|
5
5
|
ListExecutionPlansSummary,
|
|
6
6
|
ListExecutionPlansToolResultData,
|
|
7
7
|
PlanEventRecord,
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
PlanNodeRunRecord,
|
|
10
10
|
PlanNodeSpecRecord,
|
|
11
11
|
PlanSpecRecord,
|
|
12
|
-
ResumeExecutionPlanRunArgs,
|
|
13
12
|
SerializableExecutionPlan,
|
|
14
13
|
SubmitPlanTurnResultArgs,
|
|
15
14
|
SubmitExecutionNodeResultArgs,
|
|
@@ -30,7 +29,7 @@ import { databaseService } from '../db/service'
|
|
|
30
29
|
import type { DatabaseTransaction } from '../db/service'
|
|
31
30
|
import { TABLES } from '../db/tables'
|
|
32
31
|
import { readApprovalContinuationResponse } from '../runtime/approval-continuation'
|
|
33
|
-
import { extractMessageText } from '../runtime/
|
|
32
|
+
import { extractMessageText } from '../runtime/thread-chat-helpers'
|
|
34
33
|
import { toDatabaseDateTime } from '../utils/date-time'
|
|
35
34
|
import { ownershipDispatcherService } from './ownership-dispatcher.service'
|
|
36
35
|
import { planBuilderService } from './plan-builder.service'
|
|
@@ -50,7 +49,7 @@ function aggregateBlockingIssues(issues: Array<{ code: string; message: string }
|
|
|
50
49
|
function toSpecData(spec: PlanSpecRecord, patch: Partial<PlanSpecRecord> & { replacedSpecId?: RecordIdInput | null }) {
|
|
51
50
|
return {
|
|
52
51
|
organizationId: ensureRecordId(spec.organizationId, TABLES.ORGANIZATION),
|
|
53
|
-
|
|
52
|
+
threadId: ensureRecordId(spec.threadId, TABLES.THREAD),
|
|
54
53
|
title: patch.title ?? spec.title,
|
|
55
54
|
objective: patch.objective ?? spec.objective,
|
|
56
55
|
version: patch.version ?? spec.version,
|
|
@@ -105,7 +104,7 @@ function buildApprovalResponseFromMessages(
|
|
|
105
104
|
|
|
106
105
|
function buildCompiledSpecCreateData(params: {
|
|
107
106
|
organizationId: RecordIdInput
|
|
108
|
-
|
|
107
|
+
threadId: RecordIdInput
|
|
109
108
|
leadAgentId: string
|
|
110
109
|
compiled: ReturnType<typeof planCompilerService.compile>
|
|
111
110
|
version: number
|
|
@@ -114,7 +113,7 @@ function buildCompiledSpecCreateData(params: {
|
|
|
114
113
|
}) {
|
|
115
114
|
return {
|
|
116
115
|
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
117
|
-
|
|
116
|
+
threadId: ensureRecordId(params.threadId, TABLES.THREAD),
|
|
118
117
|
title: params.compiled.draft.title,
|
|
119
118
|
objective: params.compiled.draft.objective,
|
|
120
119
|
version: params.version,
|
|
@@ -136,21 +135,21 @@ function buildCompiledSpecCreateData(params: {
|
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
class ExecutionPlanService {
|
|
139
|
-
async hasActivePlan(
|
|
140
|
-
return (await planRunService.getActiveRunRecord(
|
|
138
|
+
async hasActivePlan(threadId: RecordIdInput): Promise<boolean> {
|
|
139
|
+
return (await planRunService.getActiveRunRecord(threadId)) !== null
|
|
141
140
|
}
|
|
142
141
|
|
|
143
|
-
async
|
|
144
|
-
|
|
145
|
-
options?: Partial<
|
|
142
|
+
async getActivePlanForThread(
|
|
143
|
+
threadId: RecordIdInput,
|
|
144
|
+
options?: Partial<ExecutionPlanQueryArgs> & { runId?: RecordIdInput },
|
|
146
145
|
): Promise<SerializableExecutionPlan | null> {
|
|
147
|
-
const plans = await this.
|
|
146
|
+
const plans = await this.getActivePlansForThread(threadId, options)
|
|
148
147
|
return plans[0] ?? null
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
async
|
|
152
|
-
|
|
153
|
-
options?: Partial<
|
|
150
|
+
async getActivePlansForThread(
|
|
151
|
+
threadId: RecordIdInput,
|
|
152
|
+
options?: Partial<ExecutionPlanQueryArgs> & { runId?: RecordIdInput },
|
|
154
153
|
): Promise<SerializableExecutionPlan[]> {
|
|
155
154
|
if (options?.runId) {
|
|
156
155
|
const run = await planRunService.getRunById(options.runId)
|
|
@@ -165,7 +164,7 @@ class ExecutionPlanService {
|
|
|
165
164
|
]
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
const runs = await planRunService.getActiveRunRecords(
|
|
167
|
+
const runs = await planRunService.getActiveRunRecords(threadId)
|
|
169
168
|
if (runs.length === 0) return []
|
|
170
169
|
|
|
171
170
|
return await Promise.all(
|
|
@@ -181,8 +180,8 @@ class ExecutionPlanService {
|
|
|
181
180
|
)
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
async listActivePlanSummaries(
|
|
185
|
-
const runs = await planRunService.getActiveRunRecords(
|
|
183
|
+
async listActivePlanSummaries(threadId: RecordIdInput): Promise<ListExecutionPlansToolResultData> {
|
|
184
|
+
const runs = await planRunService.getActiveRunRecords(threadId)
|
|
186
185
|
const plans: ListExecutionPlansSummary[] = await Promise.all(
|
|
187
186
|
runs.map(async (run) => {
|
|
188
187
|
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
@@ -202,7 +201,7 @@ class ExecutionPlanService {
|
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
async getActivePlanToolResult(params: {
|
|
205
|
-
|
|
204
|
+
threadId: RecordIdInput
|
|
206
205
|
runId?: string
|
|
207
206
|
includeEvents?: boolean
|
|
208
207
|
includeArtifacts?: boolean
|
|
@@ -225,36 +224,26 @@ class ExecutionPlanService {
|
|
|
225
224
|
return buildExecutionPlanToolResult({ action: 'loaded', plan, message: `Loaded execution run "${plan.title}".` })
|
|
226
225
|
}
|
|
227
226
|
|
|
228
|
-
const runs = await planRunService.getActiveRunRecords(params.
|
|
227
|
+
const runs = await planRunService.getActiveRunRecords(params.threadId)
|
|
229
228
|
if (runs.length === 0) {
|
|
230
229
|
return buildExecutionPlanToolResult({ action: 'none', plan: null, message: 'No active execution run.' })
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
const plan = await planRunService.toSerializablePlan(runs[0], serializeOptions)
|
|
234
|
-
const planSummaries = await Promise.all(
|
|
235
|
-
runs.map(async (run) => {
|
|
236
|
-
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
237
|
-
return { runId: recordIdToString(run.id, TABLES.PLAN_RUN), title: spec.title, status: run.status }
|
|
238
|
-
}),
|
|
239
|
-
)
|
|
240
233
|
|
|
241
|
-
return {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}),
|
|
250
|
-
planCount: runs.length,
|
|
251
|
-
planSummaries,
|
|
252
|
-
}
|
|
234
|
+
return buildExecutionPlanToolResult({
|
|
235
|
+
action: 'loaded',
|
|
236
|
+
plan,
|
|
237
|
+
message:
|
|
238
|
+
runs.length === 1
|
|
239
|
+
? `Loaded execution run "${plan.title}".`
|
|
240
|
+
: `Loaded ${runs.length} active execution runs. Showing most recent: "${plan.title}".`,
|
|
241
|
+
})
|
|
253
242
|
}
|
|
254
243
|
|
|
255
244
|
async createPlan(params: {
|
|
256
245
|
organizationId: RecordIdInput
|
|
257
|
-
|
|
246
|
+
threadId: RecordIdInput
|
|
258
247
|
leadAgentId: string
|
|
259
248
|
input: PlanDraft
|
|
260
249
|
}): Promise<ExecutionPlanToolResultData> {
|
|
@@ -277,7 +266,7 @@ class ExecutionPlanService {
|
|
|
277
266
|
.content(
|
|
278
267
|
buildCompiledSpecCreateData({
|
|
279
268
|
organizationId: params.organizationId,
|
|
280
|
-
|
|
269
|
+
threadId: params.threadId,
|
|
281
270
|
leadAgentId: params.leadAgentId,
|
|
282
271
|
compiled,
|
|
283
272
|
version: 1,
|
|
@@ -290,7 +279,7 @@ class ExecutionPlanService {
|
|
|
290
279
|
runId,
|
|
291
280
|
spec,
|
|
292
281
|
organizationId: params.organizationId,
|
|
293
|
-
|
|
282
|
+
threadId: params.threadId,
|
|
294
283
|
leadAgentId: params.leadAgentId,
|
|
295
284
|
nodes: compiled.nodes,
|
|
296
285
|
emittedEvents,
|
|
@@ -307,7 +296,7 @@ class ExecutionPlanService {
|
|
|
307
296
|
if (compiled.draft.schedule) {
|
|
308
297
|
const schedule = await planSchedulerService.createSchedule({
|
|
309
298
|
organizationId: params.organizationId,
|
|
310
|
-
|
|
299
|
+
threadId: params.threadId,
|
|
311
300
|
planSpecId: specId,
|
|
312
301
|
runId,
|
|
313
302
|
scheduleSpec: compiled.draft.schedule,
|
|
@@ -330,17 +319,17 @@ class ExecutionPlanService {
|
|
|
330
319
|
}
|
|
331
320
|
|
|
332
321
|
async replacePlan(params: {
|
|
333
|
-
|
|
322
|
+
threadId: RecordIdInput
|
|
334
323
|
organizationId: RecordIdInput
|
|
335
324
|
leadAgentId: string
|
|
336
325
|
input: PlanDraft & { runId: string; reason: string }
|
|
337
326
|
}): Promise<ExecutionPlanToolResultData> {
|
|
338
327
|
const activeRun = await planRunService.getRunById(params.input.runId)
|
|
339
|
-
const
|
|
328
|
+
const resolvedThreadId = activeRun.threadId
|
|
340
329
|
|
|
341
|
-
const activeRuns = await planRunService.getActiveRunRecords(
|
|
330
|
+
const activeRuns = await planRunService.getActiveRunRecords(resolvedThreadId)
|
|
342
331
|
if (activeRuns.length === 0) {
|
|
343
|
-
throw new Error('No active execution run exists for this
|
|
332
|
+
throw new Error('No active execution run exists for this thread.')
|
|
344
333
|
}
|
|
345
334
|
if (!activeRuns.some((run) => recordIdToString(run.id, TABLES.PLAN_RUN) === params.input.runId)) {
|
|
346
335
|
throw new Error('Only an active execution run can be replaced.')
|
|
@@ -399,7 +388,7 @@ class ExecutionPlanService {
|
|
|
399
388
|
.content(
|
|
400
389
|
buildCompiledSpecCreateData({
|
|
401
390
|
organizationId: params.organizationId,
|
|
402
|
-
|
|
391
|
+
threadId: resolvedThreadId,
|
|
403
392
|
leadAgentId: params.leadAgentId,
|
|
404
393
|
compiled,
|
|
405
394
|
version: supersededSpec.version + 1,
|
|
@@ -413,7 +402,7 @@ class ExecutionPlanService {
|
|
|
413
402
|
runId,
|
|
414
403
|
spec,
|
|
415
404
|
organizationId: params.organizationId,
|
|
416
|
-
|
|
405
|
+
threadId: resolvedThreadId,
|
|
417
406
|
leadAgentId: params.leadAgentId,
|
|
418
407
|
nodes: compiled.nodes,
|
|
419
408
|
emittedEvents,
|
|
@@ -440,28 +429,29 @@ class ExecutionPlanService {
|
|
|
440
429
|
}
|
|
441
430
|
|
|
442
431
|
async submitNodeResult(params: {
|
|
443
|
-
|
|
432
|
+
threadId: RecordIdInput
|
|
444
433
|
emittedBy: string
|
|
445
434
|
input: SubmitExecutionNodeResultArgs
|
|
446
435
|
}): Promise<ExecutionPlanToolResultData> {
|
|
436
|
+
const { runId, nodeId, ...result } = params.input
|
|
447
437
|
return await this.submitPlanTurnResult({
|
|
448
|
-
|
|
438
|
+
threadId: params.threadId,
|
|
449
439
|
emittedBy: params.emittedBy,
|
|
450
|
-
runId
|
|
451
|
-
nodeId
|
|
452
|
-
input:
|
|
440
|
+
runId,
|
|
441
|
+
nodeId,
|
|
442
|
+
input: result,
|
|
453
443
|
})
|
|
454
444
|
}
|
|
455
445
|
|
|
456
446
|
async submitPlanTurnResult(params: {
|
|
457
|
-
|
|
447
|
+
threadId: RecordIdInput
|
|
458
448
|
runId: string
|
|
459
449
|
nodeId: string
|
|
460
450
|
emittedBy: string
|
|
461
451
|
input: SubmitPlanTurnResultArgs
|
|
462
452
|
}): Promise<ExecutionPlanToolResultData> {
|
|
463
453
|
const result = await planExecutorService.submitNodeResult({
|
|
464
|
-
|
|
454
|
+
threadId: params.threadId,
|
|
465
455
|
runId: params.runId,
|
|
466
456
|
nodeId: params.nodeId,
|
|
467
457
|
emittedBy: params.emittedBy,
|
|
@@ -476,17 +466,16 @@ class ExecutionPlanService {
|
|
|
476
466
|
action: result.action,
|
|
477
467
|
plan,
|
|
478
468
|
message: result.message ?? `Submitted result for node "${params.nodeId}".`,
|
|
479
|
-
changedNodeId: result.changedNodeId ?? undefined,
|
|
480
469
|
})
|
|
481
470
|
}
|
|
482
471
|
|
|
483
472
|
async resumeRun(params: {
|
|
484
|
-
|
|
473
|
+
threadId: RecordIdInput
|
|
485
474
|
emittedBy: string
|
|
486
|
-
input:
|
|
475
|
+
input: { runId: string }
|
|
487
476
|
}): Promise<ExecutionPlanToolResultData> {
|
|
488
477
|
const result = await planExecutorService.resumeRun({
|
|
489
|
-
|
|
478
|
+
threadId: params.threadId,
|
|
490
479
|
runId: params.input.runId,
|
|
491
480
|
emittedBy: params.emittedBy,
|
|
492
481
|
})
|
|
@@ -499,12 +488,11 @@ class ExecutionPlanService {
|
|
|
499
488
|
action: result.action,
|
|
500
489
|
plan,
|
|
501
490
|
message: result.message ?? `Resumed execution run "${params.input.runId}".`,
|
|
502
|
-
changedNodeId: result.changedNodeId ?? undefined,
|
|
503
491
|
})
|
|
504
492
|
}
|
|
505
493
|
|
|
506
494
|
async applyApprovalResponseFromMessages(params: {
|
|
507
|
-
|
|
495
|
+
threadId: RecordIdInput
|
|
508
496
|
approvalMessages: ChatMessage[]
|
|
509
497
|
respondedBy: string
|
|
510
498
|
}): Promise<SerializableExecutionPlan | null> {
|
|
@@ -512,22 +500,22 @@ class ExecutionPlanService {
|
|
|
512
500
|
if (!approvalResponse) return null
|
|
513
501
|
|
|
514
502
|
return await this.respondToApproval({
|
|
515
|
-
|
|
503
|
+
threadId: params.threadId,
|
|
516
504
|
emittedBy: params.respondedBy,
|
|
517
505
|
input: approvalResponse,
|
|
518
506
|
})
|
|
519
507
|
}
|
|
520
508
|
|
|
521
509
|
async respondToApproval(params: {
|
|
522
|
-
|
|
510
|
+
threadId: RecordIdInput
|
|
523
511
|
emittedBy: string
|
|
524
512
|
input: { approvalId: string; response: Record<string, unknown>; approvalMessageId?: string }
|
|
525
513
|
}): Promise<SerializableExecutionPlan | null> {
|
|
526
|
-
const run = await planRunService.getActiveRunRecord(params.
|
|
514
|
+
const run = await planRunService.getActiveRunRecord(params.threadId)
|
|
527
515
|
if (!run) return null
|
|
528
516
|
|
|
529
517
|
const plan = await planExecutorService.submitHumanNodeResponse({
|
|
530
|
-
|
|
518
|
+
threadId: params.threadId,
|
|
531
519
|
approvalId: params.input.approvalId,
|
|
532
520
|
respondedBy: params.emittedBy,
|
|
533
521
|
response: params.input.response,
|
|
@@ -539,11 +527,11 @@ class ExecutionPlanService {
|
|
|
539
527
|
}
|
|
540
528
|
|
|
541
529
|
async applyHumanInputFromUserMessage(params: {
|
|
542
|
-
|
|
530
|
+
threadId: RecordIdInput
|
|
543
531
|
message: ChatMessage
|
|
544
532
|
respondedBy: string
|
|
545
533
|
}): Promise<SerializableExecutionPlan | null> {
|
|
546
|
-
const run = await planRunService.getActiveRunRecord(params.
|
|
534
|
+
const run = await planRunService.getActiveRunRecord(params.threadId)
|
|
547
535
|
if (!run || run.status !== 'awaiting-human' || !run.waitingNodeId) return null
|
|
548
536
|
|
|
549
537
|
const nodeSpec = await planRunService.getNodeSpecByNodeId(run.planSpecId, run.waitingNodeId)
|
|
@@ -565,7 +553,7 @@ class ExecutionPlanService {
|
|
|
565
553
|
} satisfies Record<string, unknown>
|
|
566
554
|
|
|
567
555
|
const plan = await planExecutorService.submitHumanNodeResponse({
|
|
568
|
-
|
|
556
|
+
threadId: params.threadId,
|
|
569
557
|
respondedBy: params.respondedBy,
|
|
570
558
|
response,
|
|
571
559
|
approvalMessageId: params.message.id,
|
|
@@ -677,7 +665,7 @@ class ExecutionPlanService {
|
|
|
677
665
|
runId: RecordIdInput
|
|
678
666
|
spec: PlanSpecRecord
|
|
679
667
|
organizationId: RecordIdInput
|
|
680
|
-
|
|
668
|
+
threadId: RecordIdInput
|
|
681
669
|
leadAgentId: string
|
|
682
670
|
nodes: CompiledPlanNode[]
|
|
683
671
|
emittedEvents: PlanEventRecord[]
|
|
@@ -694,7 +682,7 @@ class ExecutionPlanService {
|
|
|
694
682
|
.content({
|
|
695
683
|
planSpecId: ensureRecordId(params.spec.id, TABLES.PLAN_SPEC),
|
|
696
684
|
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
697
|
-
|
|
685
|
+
threadId: ensureRecordId(params.threadId, TABLES.THREAD),
|
|
698
686
|
leadAgentId: params.leadAgentId,
|
|
699
687
|
status: 'running',
|
|
700
688
|
readyNodeIds: [],
|