@lota-sdk/core 0.2.2 → 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
|
@@ -31,8 +31,8 @@ import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
|
31
31
|
import { pluginExecutorService } from './plugin-executor.service'
|
|
32
32
|
import { skillResolverService } from './skill-resolver.service'
|
|
33
33
|
import { systemExecutorService } from './system-executor.service'
|
|
34
|
+
import { ThreadSchema } from './thread.types'
|
|
34
35
|
import { userService } from './user.service'
|
|
35
|
-
import { WorkstreamSchema } from './workstream.types'
|
|
36
36
|
|
|
37
37
|
const STABLE_RUN_STATUSES = new Set(['awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
|
|
38
38
|
const MAX_DISPATCH_ITERATIONS = 64
|
|
@@ -73,8 +73,6 @@ function toArtifactSubmission(artifact: PlanArtifactRecord): PlanArtifactSubmiss
|
|
|
73
73
|
return {
|
|
74
74
|
name: artifact.name,
|
|
75
75
|
kind: artifact.kind,
|
|
76
|
-
pointer: artifact.pointer,
|
|
77
|
-
...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
|
|
78
76
|
...(artifact.description ? { description: artifact.description } : {}),
|
|
79
77
|
...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
|
|
80
78
|
}
|
|
@@ -157,7 +155,7 @@ class OwnershipDispatcherService {
|
|
|
157
155
|
if (spec.executionMode === 'graph-full') {
|
|
158
156
|
const { globalOrchestratorService } = await import('./global-orchestrator.service')
|
|
159
157
|
await globalOrchestratorService.routeGraphFull({
|
|
160
|
-
|
|
158
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
161
159
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
162
160
|
})
|
|
163
161
|
return this.serializeRun(run.id)
|
|
@@ -200,7 +198,7 @@ class OwnershipDispatcherService {
|
|
|
200
198
|
})
|
|
201
199
|
|
|
202
200
|
await planExecutorService.submitNodeResult({
|
|
203
|
-
|
|
201
|
+
threadId: run.threadId,
|
|
204
202
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
205
203
|
nodeId: planNode.id,
|
|
206
204
|
emittedBy: planNode.owner.ref,
|
|
@@ -208,7 +206,7 @@ class OwnershipDispatcherService {
|
|
|
208
206
|
})
|
|
209
207
|
} catch (error) {
|
|
210
208
|
await planExecutorService.blockNodeOnDispatchFailure({
|
|
211
|
-
|
|
209
|
+
threadId: run.threadId,
|
|
212
210
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
213
211
|
nodeId: planNode.id,
|
|
214
212
|
emittedBy: planNode.owner.ref,
|
|
@@ -283,18 +281,14 @@ class OwnershipDispatcherService {
|
|
|
283
281
|
nodeSpecRecord: PlanNodeSpecRecord,
|
|
284
282
|
): Promise<Omit<OwnershipDispatchContext, 'nodeId'>> {
|
|
285
283
|
const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
|
|
286
|
-
const
|
|
284
|
+
const threadId = recordIdToString(run.threadId, TABLES.THREAD)
|
|
287
285
|
const planId = recordIdToString(run.id, TABLES.PLAN_RUN)
|
|
288
|
-
const [
|
|
289
|
-
databaseService.findOne(
|
|
290
|
-
TABLES.WORKSTREAM,
|
|
291
|
-
{ id: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM) },
|
|
292
|
-
WorkstreamSchema,
|
|
293
|
-
),
|
|
286
|
+
const [thread, nodeSpecs, nodeRuns] = await Promise.all([
|
|
287
|
+
databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
|
|
294
288
|
planRunService.listNodeSpecs(spec.id),
|
|
295
289
|
planRunService.listNodeRuns(run.id),
|
|
296
290
|
])
|
|
297
|
-
const userId =
|
|
291
|
+
const userId = thread?.userId ? recordIdToString(thread.userId, TABLES.USER) : undefined
|
|
298
292
|
const userName = userId
|
|
299
293
|
? await userService
|
|
300
294
|
.getUser(userId)
|
|
@@ -304,8 +298,7 @@ class OwnershipDispatcherService {
|
|
|
304
298
|
const nodeSpecsById = new Map(nodeSpecs.map((candidate) => [candidate.nodeId, candidate]))
|
|
305
299
|
const upstreamHandoffs: UpstreamHandoff[] = nodeRuns
|
|
306
300
|
.filter(
|
|
307
|
-
(candidate)
|
|
308
|
-
nodeSpecRecord.upstreamNodeIds.includes(candidate.nodeId) && candidate.handoffContext !== undefined,
|
|
301
|
+
(candidate) => nodeSpecRecord.upstreamNodeIds.includes(candidate.nodeId) && candidate.latestNotes !== undefined,
|
|
309
302
|
)
|
|
310
303
|
.map((candidate) => {
|
|
311
304
|
const upstreamNodeSpec = nodeSpecsById.get(candidate.nodeId)
|
|
@@ -314,13 +307,13 @@ class OwnershipDispatcherService {
|
|
|
314
307
|
label: upstreamNodeSpec?.label ?? candidate.nodeId,
|
|
315
308
|
ownerRef: upstreamNodeSpec?.owner.ref ?? 'unknown',
|
|
316
309
|
ownerType: upstreamNodeSpec?.owner.executorType ?? 'system',
|
|
317
|
-
|
|
310
|
+
summary: candidate.latestNotes ?? undefined,
|
|
318
311
|
}
|
|
319
312
|
})
|
|
320
313
|
|
|
321
314
|
return {
|
|
322
315
|
organizationId,
|
|
323
|
-
|
|
316
|
+
threadId,
|
|
324
317
|
planId,
|
|
325
318
|
leadAgentId: run.leadAgentId,
|
|
326
319
|
...(userId ? { userId } : {}),
|
|
@@ -355,9 +348,10 @@ class OwnershipDispatcherService {
|
|
|
355
348
|
nodeId: params.nodeSpec.id,
|
|
356
349
|
config: params.nodeSpec.monitoringConfig,
|
|
357
350
|
organizationId: params.context.organizationId,
|
|
358
|
-
|
|
351
|
+
threadId: params.context.threadId,
|
|
359
352
|
})
|
|
360
353
|
return {
|
|
354
|
+
notes: `Monitoring window started for node "${params.nodeSpec.label}".`,
|
|
361
355
|
structuredOutput: { status: 'monitoring-started', config: params.nodeSpec.monitoringConfig },
|
|
362
356
|
artifacts: [],
|
|
363
357
|
}
|
|
@@ -6,7 +6,7 @@ import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-vi
|
|
|
6
6
|
import { planAgentQueryService } from './plan-agent-query.service'
|
|
7
7
|
import { planExecutorService } from './plan-executor.service'
|
|
8
8
|
import { planRunService } from './plan-run.service'
|
|
9
|
-
import {
|
|
9
|
+
import { threadService } from './thread.service'
|
|
10
10
|
|
|
11
11
|
const PLAN_AGENT_HEARTBEAT_LOCK_TTL_MS = 300_000
|
|
12
12
|
const PLAN_AGENT_HEARTBEAT_LOCK_REFRESH_MS = 20_000
|
|
@@ -17,26 +17,26 @@ function buildHeartbeatLockKey(runId: string, nodeId: string): string {
|
|
|
17
17
|
|
|
18
18
|
function buildWakeDedupeKey(params: {
|
|
19
19
|
organizationId: string
|
|
20
|
-
|
|
20
|
+
threadId: string
|
|
21
21
|
runId: string
|
|
22
22
|
nodeId: string
|
|
23
23
|
agentId: string
|
|
24
24
|
}) {
|
|
25
|
-
return `${params.organizationId}:${params.
|
|
25
|
+
return `${params.organizationId}:${params.threadId}:${params.runId}:${params.nodeId}:${params.agentId}`
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
class PlanAgentHeartbeatService {
|
|
29
29
|
async wakeNode(params: {
|
|
30
30
|
organizationId: string
|
|
31
|
-
|
|
31
|
+
threadId: string
|
|
32
32
|
runId: string
|
|
33
33
|
nodeId: string
|
|
34
34
|
agentId: string
|
|
35
35
|
reason: string
|
|
36
36
|
}): Promise<boolean> {
|
|
37
|
-
const
|
|
38
|
-
await
|
|
39
|
-
if (await
|
|
37
|
+
const threadRef = ensureRecordId(params.threadId, TABLES.THREAD)
|
|
38
|
+
await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
|
|
39
|
+
if (await threadService.getActiveRunId(threadRef)) {
|
|
40
40
|
return false
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -51,11 +51,11 @@ class PlanAgentHeartbeatService {
|
|
|
51
51
|
logger: serverLogger,
|
|
52
52
|
},
|
|
53
53
|
async () => {
|
|
54
|
-
await
|
|
55
|
-
if (await
|
|
54
|
+
await threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
|
|
55
|
+
if (await threadService.hasActiveRunLease(threadRef)) {
|
|
56
56
|
return false
|
|
57
57
|
}
|
|
58
|
-
if (await
|
|
58
|
+
if (await threadService.getActiveRunId(threadRef)) {
|
|
59
59
|
return false
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -83,7 +83,7 @@ class PlanAgentHeartbeatService {
|
|
|
83
83
|
await planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId })
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const { triggerPlanNodeTurn } = await import('./
|
|
86
|
+
const { triggerPlanNodeTurn } = await import('./thread-turn')
|
|
87
87
|
|
|
88
88
|
await triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId })
|
|
89
89
|
return true
|
|
@@ -101,7 +101,7 @@ class PlanAgentHeartbeatService {
|
|
|
101
101
|
|
|
102
102
|
const wakeTargets = new Map<
|
|
103
103
|
string,
|
|
104
|
-
{ organizationId: string;
|
|
104
|
+
{ organizationId: string; threadId: string; runId: string; nodeId: string; agentId: string; reason: string }
|
|
105
105
|
>()
|
|
106
106
|
|
|
107
107
|
for (const node of actionable) {
|
|
@@ -118,7 +118,7 @@ class PlanAgentHeartbeatService {
|
|
|
118
118
|
}
|
|
119
119
|
const wakeTarget = {
|
|
120
120
|
organizationId: node.organizationId,
|
|
121
|
-
|
|
121
|
+
threadId: node.threadId,
|
|
122
122
|
runId: node.runId,
|
|
123
123
|
nodeId: node.nodeId,
|
|
124
124
|
agentId: node.agentId,
|
|
@@ -15,7 +15,7 @@ const DEADLINE_TRACKED_NODE_STATUSES = new Set(['ready', 'running', 'awaiting-hu
|
|
|
15
15
|
|
|
16
16
|
export interface ActionablePlanAgentNode {
|
|
17
17
|
organizationId: string
|
|
18
|
-
|
|
18
|
+
threadId: string
|
|
19
19
|
runId: string
|
|
20
20
|
nodeId: string
|
|
21
21
|
agentId: string
|
|
@@ -25,7 +25,7 @@ export interface ActionablePlanAgentNode {
|
|
|
25
25
|
|
|
26
26
|
export interface ApproachingDeadlineNode {
|
|
27
27
|
organizationId: string
|
|
28
|
-
|
|
28
|
+
threadId: string
|
|
29
29
|
runId: string
|
|
30
30
|
nodeId: string
|
|
31
31
|
agentId?: string
|
|
@@ -37,7 +37,7 @@ export interface ApproachingDeadlineNode {
|
|
|
37
37
|
|
|
38
38
|
export interface RecentlyUnblockedNode {
|
|
39
39
|
organizationId: string
|
|
40
|
-
|
|
40
|
+
threadId: string
|
|
41
41
|
runId: string
|
|
42
42
|
nodeId: string
|
|
43
43
|
agentId: string
|
|
@@ -89,7 +89,7 @@ class PlanAgentQueryService {
|
|
|
89
89
|
if (params.agentId && params.agentId !== visibleTarget.agentId) continue
|
|
90
90
|
actionable.push({
|
|
91
91
|
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
92
|
-
|
|
92
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
93
93
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
94
94
|
nodeId: nodeSpec.nodeId,
|
|
95
95
|
agentId: visibleTarget.agentId,
|
|
@@ -124,7 +124,7 @@ class PlanAgentQueryService {
|
|
|
124
124
|
|
|
125
125
|
actionable.push({
|
|
126
126
|
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
127
|
-
|
|
127
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
128
128
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
129
129
|
nodeId: nodeSpec.nodeId,
|
|
130
130
|
agentId: visibleTarget.agentId,
|
|
@@ -176,7 +176,7 @@ class PlanAgentQueryService {
|
|
|
176
176
|
const visibleTarget = isVisibleAgentNode({ nodeSpec, spec })
|
|
177
177
|
matches.push({
|
|
178
178
|
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
179
|
-
|
|
179
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
180
180
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
181
181
|
nodeId: nodeSpec.nodeId,
|
|
182
182
|
...(visibleTarget ? { agentId: visibleTarget.agentId, visibility: visibleTarget.visibility } : {}),
|
|
@@ -232,7 +232,7 @@ class PlanAgentQueryService {
|
|
|
232
232
|
|
|
233
233
|
matches.push({
|
|
234
234
|
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
235
|
-
|
|
235
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
236
236
|
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
237
237
|
nodeId: currentNodeId,
|
|
238
238
|
agentId: visibleTarget.agentId,
|
|
@@ -28,8 +28,7 @@ class PlanArtifactService {
|
|
|
28
28
|
nodeId: params.nodeId,
|
|
29
29
|
name: artifact.name,
|
|
30
30
|
kind: artifact.kind,
|
|
31
|
-
pointer: artifact.
|
|
32
|
-
...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
|
|
31
|
+
pointer: `artifact://${params.nodeId}/${artifact.name}`,
|
|
33
32
|
...(artifact.description ? { description: artifact.description } : {}),
|
|
34
33
|
...(artifact.payload ? { payload: artifact.payload } : {}),
|
|
35
34
|
})
|
|
@@ -13,7 +13,7 @@ class PlanCoordinationService {
|
|
|
13
13
|
/**
|
|
14
14
|
* Resolve cross-plan artifact dependencies.
|
|
15
15
|
* For each dependency:
|
|
16
|
-
* 1. Find the source plan by title in the
|
|
16
|
+
* 1. Find the source plan by title in the thread
|
|
17
17
|
* 2. Find the artifact by (nodeId, artifactName) in that plan's run
|
|
18
18
|
* 3. Check staleness if maxStalenessMs set
|
|
19
19
|
* 4. Based on triggerMode:
|
|
@@ -23,7 +23,7 @@ class PlanCoordinationService {
|
|
|
23
23
|
*/
|
|
24
24
|
async resolveDependencies(params: {
|
|
25
25
|
dependencies: PlanDependency[]
|
|
26
|
-
|
|
26
|
+
threadId: string
|
|
27
27
|
}): Promise<DependencyResolutionResult> {
|
|
28
28
|
const { planRunService } = await import('./plan-run.service')
|
|
29
29
|
|
|
@@ -34,10 +34,10 @@ class PlanCoordinationService {
|
|
|
34
34
|
for (const dep of params.dependencies) {
|
|
35
35
|
const depKey = `${dep.sourcePlanTitle}:${dep.sourceNodeId}:${dep.artifactName}`
|
|
36
36
|
|
|
37
|
-
const specs = await planRunService.
|
|
37
|
+
const specs = await planRunService.listPlanSpecsByThread(params.threadId)
|
|
38
38
|
const sourceSpec = specs.find((s) => s.title === dep.sourcePlanTitle)
|
|
39
39
|
if (!sourceSpec) {
|
|
40
|
-
const reason = `Source plan "${dep.sourcePlanTitle}" not found in
|
|
40
|
+
const reason = `Source plan "${dep.sourcePlanTitle}" not found in thread.`
|
|
41
41
|
if (dep.triggerMode === 'block') {
|
|
42
42
|
unresolved.push(dep)
|
|
43
43
|
} else if (dep.triggerMode === 'notify') {
|
|
@@ -64,7 +64,7 @@ class PlanCycleService {
|
|
|
64
64
|
|
|
65
65
|
async createCycle(params: {
|
|
66
66
|
organizationId: RecordIdInput
|
|
67
|
-
|
|
67
|
+
threadId: RecordIdInput
|
|
68
68
|
templateId: RecordIdInput
|
|
69
69
|
name: string
|
|
70
70
|
schedule: CycleSchedule
|
|
@@ -77,7 +77,7 @@ class PlanCycleService {
|
|
|
77
77
|
TABLES.PLAN_CYCLE,
|
|
78
78
|
{
|
|
79
79
|
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
80
|
-
|
|
80
|
+
threadId: ensureRecordId(params.threadId, TABLES.THREAD),
|
|
81
81
|
templateId: ensureRecordId(params.templateId, TABLES.PLAN_TEMPLATE),
|
|
82
82
|
name: params.name,
|
|
83
83
|
schedule: params.schedule,
|
|
@@ -92,7 +92,7 @@ class PlanCycleService {
|
|
|
92
92
|
const createResult = await planTemplateService.instantiate({
|
|
93
93
|
templateId: params.templateId,
|
|
94
94
|
organizationId: params.organizationId,
|
|
95
|
-
|
|
95
|
+
threadId: params.threadId,
|
|
96
96
|
leadAgentId: params.leadAgentId,
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -101,7 +101,7 @@ class PlanCycleService {
|
|
|
101
101
|
const scheduleSpec = cycleScheduleToSpec(params.schedule)
|
|
102
102
|
const scheduleRecord = await planSchedulerService.createSchedule({
|
|
103
103
|
organizationId: params.organizationId,
|
|
104
|
-
|
|
104
|
+
threadId: params.threadId,
|
|
105
105
|
...(createdRunId ? { runId: ensureRecordId(createdRunId, TABLES.PLAN_RUN) } : {}),
|
|
106
106
|
scheduleSpec,
|
|
107
107
|
})
|
|
@@ -160,7 +160,7 @@ class PlanCycleService {
|
|
|
160
160
|
const result = await planTemplateService.instantiate({
|
|
161
161
|
templateId: cycle.templateId,
|
|
162
162
|
organizationId: cycle.organizationId,
|
|
163
|
-
|
|
163
|
+
threadId: cycle.threadId,
|
|
164
164
|
leadAgentId: 'system',
|
|
165
165
|
overrides: draft,
|
|
166
166
|
})
|
|
@@ -263,10 +263,10 @@ class PlanCycleService {
|
|
|
263
263
|
)
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
async listCycles(
|
|
266
|
+
async listCycles(threadId: RecordIdInput): Promise<PlanCycleRecord[]> {
|
|
267
267
|
return databaseService.findMany(
|
|
268
268
|
TABLES.PLAN_CYCLE,
|
|
269
|
-
{
|
|
269
|
+
{ threadId: ensureRecordId(threadId, TABLES.THREAD) },
|
|
270
270
|
PlanCycleRecordSchema,
|
|
271
271
|
{ orderBy: 'createdAt', orderDir: 'ASC' },
|
|
272
272
|
)
|
|
@@ -126,7 +126,7 @@ class PlanDeadlineService {
|
|
|
126
126
|
await this.applyDeadlineMissAction({
|
|
127
127
|
runId: entry.nodeRun.runId,
|
|
128
128
|
nodeId: entry.nodeRun.nodeId,
|
|
129
|
-
|
|
129
|
+
threadId: recordIdToString(run.threadId, TABLES.THREAD),
|
|
130
130
|
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
131
131
|
action: deadline.missAction,
|
|
132
132
|
emittedBy: 'plan-deadline-checker',
|
|
@@ -228,7 +228,7 @@ class PlanDeadlineService {
|
|
|
228
228
|
async applyDeadlineMissAction(params: {
|
|
229
229
|
runId: RecordIdInput
|
|
230
230
|
nodeId: string
|
|
231
|
-
|
|
231
|
+
threadId: string
|
|
232
232
|
organizationId: string
|
|
233
233
|
action: DeadlineAction
|
|
234
234
|
emittedBy: string
|
|
@@ -275,7 +275,7 @@ class PlanDeadlineService {
|
|
|
275
275
|
})
|
|
276
276
|
const { planExecutorService } = await import('./plan-executor.service')
|
|
277
277
|
await planExecutorService.blockNodeOnDispatchFailure({
|
|
278
|
-
|
|
278
|
+
threadId: params.threadId,
|
|
279
279
|
runId: runIdStr,
|
|
280
280
|
nodeId: params.nodeId,
|
|
281
281
|
emittedBy: params.emittedBy,
|
|
@@ -296,7 +296,7 @@ class PlanDeadlineService {
|
|
|
296
296
|
})
|
|
297
297
|
const { planExecutorService } = await import('./plan-executor.service')
|
|
298
298
|
await planExecutorService.blockNodeOnDispatchFailure({
|
|
299
|
-
|
|
299
|
+
threadId: params.threadId,
|
|
300
300
|
runId: runIdStr,
|
|
301
301
|
nodeId: params.nodeId,
|
|
302
302
|
emittedBy: params.emittedBy,
|
|
@@ -9,8 +9,8 @@ import { resolvePlanNodeExecutionVisibility } from '../runtime/execution-plan-vi
|
|
|
9
9
|
import { getRuntimeAdapters } from '../runtime/runtime-extensions'
|
|
10
10
|
import type { LotaRuntimePlanEventEnvelope } from '../runtime/runtime-extensions'
|
|
11
11
|
import { planRunService } from './plan-run.service'
|
|
12
|
+
import { ThreadSchema } from './thread.types'
|
|
12
13
|
import { userService } from './user.service'
|
|
13
|
-
import { WorkstreamSchema } from './workstream.types'
|
|
14
14
|
|
|
15
15
|
const PLAN_EVENT_DELIVERED_TTL_MS = 7 * 24 * 60 * 60 * 1000
|
|
16
16
|
|
|
@@ -41,24 +41,20 @@ class PlanEventDeliveryService {
|
|
|
41
41
|
private async deliverEvent(rawEvent: PlanEventRecord): Promise<void> {
|
|
42
42
|
const event = PlanEventSchema.parse(rawEvent)
|
|
43
43
|
const run = await planRunService.getRunById(event.runId)
|
|
44
|
-
const [spec, nodeSpecs, nodeRuns,
|
|
44
|
+
const [spec, nodeSpecs, nodeRuns, thread] = await Promise.all([
|
|
45
45
|
planRunService.getPlanSpecById(run.planSpecId),
|
|
46
46
|
planRunService.listNodeSpecs(run.planSpecId),
|
|
47
47
|
planRunService.listNodeRuns(run.id),
|
|
48
|
-
databaseService.findOne(
|
|
49
|
-
TABLES.WORKSTREAM,
|
|
50
|
-
{ id: ensureRecordId(run.workstreamId, TABLES.WORKSTREAM) },
|
|
51
|
-
WorkstreamSchema,
|
|
52
|
-
),
|
|
48
|
+
databaseService.findOne(TABLES.THREAD, { id: ensureRecordId(run.threadId, TABLES.THREAD) }, ThreadSchema),
|
|
53
49
|
])
|
|
54
50
|
const nodeSpecsById = new Map(nodeSpecs.map((nodeSpec) => [nodeSpec.nodeId, nodeSpec]))
|
|
55
51
|
const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
|
|
56
52
|
const organizationId = recordIdToString(run.organizationId, TABLES.ORGANIZATION)
|
|
57
|
-
const
|
|
53
|
+
const threadId = recordIdToString(run.threadId, TABLES.THREAD)
|
|
58
54
|
const runIdString = recordIdToString(run.id, TABLES.PLAN_RUN)
|
|
59
55
|
const planSpecId = recordIdToString(spec.id, TABLES.PLAN_SPEC)
|
|
60
|
-
const userId =
|
|
61
|
-
? recordIdToString(ensureRecordId(
|
|
56
|
+
const userId = thread?.userId
|
|
57
|
+
? recordIdToString(ensureRecordId(thread.userId, TABLES.USER), TABLES.USER)
|
|
62
58
|
: undefined
|
|
63
59
|
const userName =
|
|
64
60
|
userId === undefined
|
|
@@ -75,7 +71,7 @@ class PlanEventDeliveryService {
|
|
|
75
71
|
...(event.nodeId ? { nodeSpec: nodeSpecsById.get(event.nodeId) } : {}),
|
|
76
72
|
...(event.nodeId ? { nodeRun: nodeRunsById.get(event.nodeId) } : {}),
|
|
77
73
|
organizationId,
|
|
78
|
-
|
|
74
|
+
threadId,
|
|
79
75
|
runId: runIdString,
|
|
80
76
|
planSpecId,
|
|
81
77
|
...(userId ? { userId } : {}),
|
|
@@ -123,7 +119,7 @@ class PlanEventDeliveryService {
|
|
|
123
119
|
const { enqueuePlanAgentHeartbeatWake } = await import('../queues/plan-agent-heartbeat.queue')
|
|
124
120
|
await enqueuePlanAgentHeartbeatWake({
|
|
125
121
|
organizationId: envelope.organizationId,
|
|
126
|
-
|
|
122
|
+
threadId: envelope.threadId,
|
|
127
123
|
runId: envelope.runId,
|
|
128
124
|
nodeId: wakeTarget.nodeId,
|
|
129
125
|
agentId: wakeTarget.agentId,
|
|
@@ -92,20 +92,15 @@ function parseLiteralValue(raw: string): unknown {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function buildArtifactContext(
|
|
96
|
-
artifacts: Array<{ name: string; kind: string; pointer: string; schemaRef?: string; payload?: unknown }>,
|
|
97
|
-
) {
|
|
95
|
+
function buildArtifactContext(artifacts: Array<{ name: string; kind: string; payload?: unknown }>) {
|
|
98
96
|
return Object.fromEntries(
|
|
99
|
-
artifacts.map((artifact) => [
|
|
100
|
-
artifact.name,
|
|
101
|
-
{ kind: artifact.kind, pointer: artifact.pointer, schemaRef: artifact.schemaRef, payload: artifact.payload },
|
|
102
|
-
]),
|
|
97
|
+
artifacts.map((artifact) => [artifact.name, { kind: artifact.kind, payload: artifact.payload }]),
|
|
103
98
|
)
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
function buildNodeContext(params: {
|
|
107
102
|
nodeRun: PlanNodeRunRecord | undefined
|
|
108
|
-
artifacts: Array<{ name: string; kind: string;
|
|
103
|
+
artifacts: Array<{ name: string; kind: string; payload?: unknown }>
|
|
109
104
|
}) {
|
|
110
105
|
return {
|
|
111
106
|
input: params.nodeRun?.resolvedInput ?? {},
|
|
@@ -159,7 +154,6 @@ type PlanNodeRunUpdate = Omit<
|
|
|
159
154
|
| 'resolvedInput'
|
|
160
155
|
| 'latestStructuredOutput'
|
|
161
156
|
| 'latestNotes'
|
|
162
|
-
| 'handoffContext'
|
|
163
157
|
| 'latestAttemptId'
|
|
164
158
|
| 'scheduledAt'
|
|
165
159
|
| 'readyAt'
|
|
@@ -171,7 +165,6 @@ type PlanNodeRunUpdate = Omit<
|
|
|
171
165
|
resolvedInput?: Record<string, unknown> | null
|
|
172
166
|
latestStructuredOutput?: Record<string, unknown> | null
|
|
173
167
|
latestNotes?: string | null
|
|
174
|
-
handoffContext?: Record<string, unknown> | null
|
|
175
168
|
latestAttemptId?: RecordIdInput | null
|
|
176
169
|
scheduledAt?: string | Date | null
|
|
177
170
|
readyAt?: string | Date | null
|
|
@@ -208,13 +201,6 @@ function toNodeRunData(nodeRun: PlanNodeRunRecord, patch: PlanNodeRunUpdate) {
|
|
|
208
201
|
: nodeRun.latestNotes
|
|
209
202
|
? { latestNotes: nodeRun.latestNotes }
|
|
210
203
|
: {}),
|
|
211
|
-
...(patch.handoffContext === null
|
|
212
|
-
? {}
|
|
213
|
-
: patch.handoffContext !== undefined
|
|
214
|
-
? { handoffContext: patch.handoffContext }
|
|
215
|
-
: nodeRun.handoffContext
|
|
216
|
-
? { handoffContext: nodeRun.handoffContext }
|
|
217
|
-
: {}),
|
|
218
204
|
...(patch.latestAttemptId === null
|
|
219
205
|
? {}
|
|
220
206
|
: patch.latestAttemptId !== undefined
|
|
@@ -280,17 +266,15 @@ function deriveApprovalStatus(response: Record<string, unknown>): 'approved' | '
|
|
|
280
266
|
|
|
281
267
|
class PlanExecutorService {
|
|
282
268
|
async submitNodeResult(params: {
|
|
283
|
-
|
|
269
|
+
threadId: RecordIdInput
|
|
284
270
|
runId: string
|
|
285
271
|
nodeId: string
|
|
286
272
|
emittedBy: string
|
|
287
273
|
result: PlanNodeResultSubmission
|
|
288
274
|
}): Promise<ExecutionPlanToolResultData> {
|
|
289
275
|
const run = await planRunService.getRunById(params.runId)
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
) {
|
|
293
|
-
throw new Error('Execution node result targets a different workstream.')
|
|
276
|
+
if (recordIdToString(run.threadId, TABLES.THREAD) !== recordIdToString(params.threadId, TABLES.THREAD)) {
|
|
277
|
+
throw new Error('Execution node result targets a different thread.')
|
|
294
278
|
}
|
|
295
279
|
if (run.status === 'completed' || run.status === 'failed' || run.status === 'aborted') {
|
|
296
280
|
throw new Error('Execution run is no longer active.')
|
|
@@ -375,8 +359,7 @@ class PlanExecutorService {
|
|
|
375
359
|
attemptCount: nodeRun.attemptCount + 1,
|
|
376
360
|
latestAttemptId: finalizedAttempt.id,
|
|
377
361
|
latestStructuredOutput: params.result.structuredOutput ?? null,
|
|
378
|
-
latestNotes: params.result.notes
|
|
379
|
-
handoffContext: params.result.handoffContext ?? null,
|
|
362
|
+
latestNotes: params.result.notes,
|
|
380
363
|
}),
|
|
381
364
|
)
|
|
382
365
|
.output('after'),
|
|
@@ -659,8 +642,7 @@ class PlanExecutorService {
|
|
|
659
642
|
status: validation.warnings.length > 0 ? 'partial' : 'completed',
|
|
660
643
|
latestAttemptId: finalizedAttempt.id,
|
|
661
644
|
latestStructuredOutput: params.result.structuredOutput ?? null,
|
|
662
|
-
latestNotes: params.result.notes
|
|
663
|
-
handoffContext: params.result.handoffContext ?? null,
|
|
645
|
+
latestNotes: params.result.notes,
|
|
664
646
|
blockedReason: null,
|
|
665
647
|
failureClass: null,
|
|
666
648
|
completedAt: new Date(),
|
|
@@ -751,18 +733,17 @@ class PlanExecutorService {
|
|
|
751
733
|
action: 'node-result-submitted',
|
|
752
734
|
plan: snapshot,
|
|
753
735
|
message: `Submitted result for node "${nodeSpec.label}".`,
|
|
754
|
-
changedNodeId: params.nodeId,
|
|
755
736
|
})
|
|
756
737
|
}
|
|
757
738
|
|
|
758
739
|
async submitHumanNodeResponse(params: {
|
|
759
|
-
|
|
740
|
+
threadId: RecordIdInput
|
|
760
741
|
approvalId?: string
|
|
761
742
|
respondedBy: string
|
|
762
743
|
response: Record<string, unknown>
|
|
763
744
|
approvalMessageId?: string
|
|
764
745
|
}): Promise<SerializableExecutionPlan | null> {
|
|
765
|
-
const run = await planRunService.getActiveRunRecord(params.
|
|
746
|
+
const run = await planRunService.getActiveRunRecord(params.threadId)
|
|
766
747
|
if (!run || run.status !== 'awaiting-human' || !run.waitingNodeId) {
|
|
767
748
|
return null
|
|
768
749
|
}
|
|
@@ -790,7 +771,7 @@ class PlanExecutorService {
|
|
|
790
771
|
result: {
|
|
791
772
|
structuredOutput: params.response,
|
|
792
773
|
artifacts: [],
|
|
793
|
-
notes: typeof params.response.comments === 'string' ? params.response.comments :
|
|
774
|
+
notes: typeof params.response.comments === 'string' ? params.response.comments : 'Human response submitted.',
|
|
794
775
|
},
|
|
795
776
|
})
|
|
796
777
|
const emittedEvents: PlanEventRecord[] = []
|
|
@@ -818,7 +799,7 @@ class PlanExecutorService {
|
|
|
818
799
|
result: {
|
|
819
800
|
structuredOutput: params.response,
|
|
820
801
|
artifacts: [],
|
|
821
|
-
notes: typeof params.response.comments === 'string' ? params.response.comments :
|
|
802
|
+
notes: typeof params.response.comments === 'string' ? params.response.comments : 'Human response submitted.',
|
|
822
803
|
},
|
|
823
804
|
status: validation.blocking.length > 0 ? 'failed' : 'completed',
|
|
824
805
|
failureClass: validation.failureClass,
|
|
@@ -860,7 +841,6 @@ class PlanExecutorService {
|
|
|
860
841
|
latestAttemptId: attempt.id,
|
|
861
842
|
latestStructuredOutput: params.response,
|
|
862
843
|
latestNotes: typeof params.response.comments === 'string' ? params.response.comments : null,
|
|
863
|
-
handoffContext: null,
|
|
864
844
|
blockedReason: validation.blocking[0]?.message ?? null,
|
|
865
845
|
failureClass: validation.failureClass,
|
|
866
846
|
}),
|
|
@@ -877,7 +857,6 @@ class PlanExecutorService {
|
|
|
877
857
|
latestAttemptId: attempt.id,
|
|
878
858
|
latestStructuredOutput: params.response,
|
|
879
859
|
latestNotes: typeof params.response.comments === 'string' ? params.response.comments : null,
|
|
880
|
-
handoffContext: null,
|
|
881
860
|
blockedReason: null,
|
|
882
861
|
failureClass: null,
|
|
883
862
|
completedAt: new Date(),
|
|
@@ -981,7 +960,7 @@ class PlanExecutorService {
|
|
|
981
960
|
}
|
|
982
961
|
|
|
983
962
|
async resumeRun(params: {
|
|
984
|
-
|
|
963
|
+
threadId: RecordIdInput
|
|
985
964
|
runId: string
|
|
986
965
|
emittedBy: string
|
|
987
966
|
}): Promise<ExecutionPlanToolResultData> {
|
|
@@ -1106,7 +1085,7 @@ class PlanExecutorService {
|
|
|
1106
1085
|
}
|
|
1107
1086
|
|
|
1108
1087
|
async blockNodeOnDispatchFailure(params: {
|
|
1109
|
-
|
|
1088
|
+
threadId: RecordIdInput
|
|
1110
1089
|
runId: string
|
|
1111
1090
|
nodeId: string
|
|
1112
1091
|
emittedBy: string
|
|
@@ -1297,7 +1276,7 @@ class PlanExecutorService {
|
|
|
1297
1276
|
if (params.spec.dependencies && params.spec.dependencies.length > 0) {
|
|
1298
1277
|
const { unresolved } = await planCoordinationService.resolveDependencies({
|
|
1299
1278
|
dependencies: params.spec.dependencies,
|
|
1300
|
-
|
|
1279
|
+
threadId: recordIdToString(params.spec.threadId, TABLES.THREAD),
|
|
1301
1280
|
})
|
|
1302
1281
|
if (unresolved.length > 0) {
|
|
1303
1282
|
currentRun = await this.replaceRun(params.tx, currentRun, { status: 'blocked', readyNodeIds: [] })
|
|
@@ -1405,7 +1384,7 @@ class PlanExecutorService {
|
|
|
1405
1384
|
replaceNodeRun(scheduledNodeRun)
|
|
1406
1385
|
await planSchedulerService.createSchedule({
|
|
1407
1386
|
organizationId: currentRun.organizationId,
|
|
1408
|
-
|
|
1387
|
+
threadId: currentRun.threadId,
|
|
1409
1388
|
planSpecId: params.spec.id,
|
|
1410
1389
|
runId: currentRun.id,
|
|
1411
1390
|
nodeId: nodeSpec.nodeId,
|