@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
|
@@ -55,7 +55,7 @@ class GlobalOrchestratorService {
|
|
|
55
55
|
return 'skip'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async routeGraphFull(params: {
|
|
58
|
+
async routeGraphFull(params: { threadId: string; runId: string }): Promise<void> {
|
|
59
59
|
const MAX_ROUNDS = 32
|
|
60
60
|
const STRUCTURAL_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
|
|
61
61
|
|
|
@@ -105,7 +105,7 @@ class GlobalOrchestratorService {
|
|
|
105
105
|
if (!ns || ns.owner.executorType !== 'agent') continue
|
|
106
106
|
await enqueuePlanAgentHeartbeatWake({
|
|
107
107
|
organizationId: recordIdToString(updatedRunForWake.organizationId, TABLES.ORGANIZATION),
|
|
108
|
-
|
|
108
|
+
threadId: recordIdToString(updatedRunForWake.threadId, TABLES.THREAD),
|
|
109
109
|
runId: recordIdToString(updatedRunForWake.id, TABLES.PLAN_RUN),
|
|
110
110
|
nodeId: nodeRun.nodeId,
|
|
111
111
|
agentId: ns.owner.ref,
|
|
@@ -140,7 +140,7 @@ class GlobalOrchestratorService {
|
|
|
140
140
|
}),
|
|
141
141
|
)
|
|
142
142
|
|
|
143
|
-
const
|
|
143
|
+
const threadId = recordIdToString(updatedRun.threadId, TABLES.THREAD)
|
|
144
144
|
const runId = recordIdToString(updatedRun.id, TABLES.PLAN_RUN)
|
|
145
145
|
|
|
146
146
|
// Submit results sequentially (each triggers syncRunGraph internally)
|
|
@@ -151,7 +151,7 @@ class GlobalOrchestratorService {
|
|
|
151
151
|
|
|
152
152
|
if (settled.status === 'fulfilled') {
|
|
153
153
|
await planExecutorService.submitNodeResult({
|
|
154
|
-
|
|
154
|
+
threadId,
|
|
155
155
|
runId,
|
|
156
156
|
nodeId: settled.value.nodeId,
|
|
157
157
|
emittedBy: settled.value.ownerRef,
|
|
@@ -160,7 +160,7 @@ class GlobalOrchestratorService {
|
|
|
160
160
|
} else {
|
|
161
161
|
serverLogger.warn`routeGraphFull: dispatch failed for node "${nodeRun.nodeId}": ${settled.reason}`
|
|
162
162
|
await planExecutorService.blockNodeOnDispatchFailure({
|
|
163
|
-
|
|
163
|
+
threadId,
|
|
164
164
|
runId,
|
|
165
165
|
nodeId: nodeRun.nodeId,
|
|
166
166
|
emittedBy: nodeSpecRecord?.owner.ref ?? 'unknown',
|
package/src/services/index.ts
CHANGED
|
@@ -35,10 +35,10 @@ export * from './skill-resolver.service'
|
|
|
35
35
|
export * from './social-chat-history.service'
|
|
36
36
|
export * from './system-executor.service'
|
|
37
37
|
export * from './user.service'
|
|
38
|
-
export * from './
|
|
39
|
-
export * from './
|
|
40
|
-
export * from './
|
|
41
|
-
export * from './
|
|
42
|
-
export * from './
|
|
43
|
-
export * from './
|
|
38
|
+
export * from './thread-message.service'
|
|
39
|
+
export * from './thread.types'
|
|
40
|
+
export * from './thread-title.service'
|
|
41
|
+
export * from './thread-turn'
|
|
42
|
+
export * from './thread-plan-registry.service'
|
|
43
|
+
export * from './thread.service'
|
|
44
44
|
export * from './write-intent-validator.service'
|
|
@@ -209,7 +209,7 @@ class MemoryService {
|
|
|
209
209
|
MAX_CONVERSATION_MEMORY_BLOCK_CHARS,
|
|
210
210
|
)
|
|
211
211
|
if (normalizedMemoryBlock) {
|
|
212
|
-
messages.push({ role: 'user', content: `
|
|
212
|
+
messages.push({ role: 'user', content: `Thread memory block:\n${normalizedMemoryBlock}` })
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
const normalizedAttachmentContext = this.normalizeConversationText(
|
|
@@ -20,11 +20,11 @@ class MonitoringWindowService {
|
|
|
20
20
|
nodeId: string
|
|
21
21
|
config: MonitoringWindowConfig
|
|
22
22
|
organizationId: string
|
|
23
|
-
|
|
23
|
+
threadId: string
|
|
24
24
|
}): Promise<void> {
|
|
25
25
|
await planSchedulerService.createSchedule({
|
|
26
26
|
organizationId: params.organizationId,
|
|
27
|
-
|
|
27
|
+
threadId: params.threadId,
|
|
28
28
|
runId: params.runId,
|
|
29
29
|
nodeId: params.nodeId,
|
|
30
30
|
scheduleSpec: {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { RecordIdRef } from '../db/record-id'
|
|
2
2
|
import { ensureRecordId } from '../db/record-id'
|
|
3
3
|
import { TABLES } from '../db/tables'
|
|
4
|
-
import { extractMessageText } from '../runtime/
|
|
5
|
-
import {
|
|
4
|
+
import { extractMessageText } from '../runtime/thread-chat-helpers'
|
|
5
|
+
import { threadMessageService } from './thread-message.service'
|
|
6
6
|
|
|
7
7
|
const APPROVAL_VERIFICATION_MESSAGE_WINDOW = 20
|
|
8
8
|
|
|
9
9
|
type VerifyMutatingApproval = (params: {
|
|
10
|
-
|
|
10
|
+
threadId: string
|
|
11
11
|
approvalReason: string
|
|
12
12
|
approvalToken: string
|
|
13
13
|
approvalMessageId?: string
|
|
@@ -58,8 +58,8 @@ function messageContainsExactApproval(messageText: string, approvalToken: string
|
|
|
58
58
|
return extracted.token === approvalToken && extracted.reason === approvalReason
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async function
|
|
62
|
-
|
|
61
|
+
async function verifyMutatingApprovalForThread(
|
|
62
|
+
threadId: RecordIdRef,
|
|
63
63
|
params: { approvalReason: string; approvalToken: string; approvalMessageId?: string },
|
|
64
64
|
): Promise<{ ok: true } | { ok: false; reason: string }> {
|
|
65
65
|
const token = extractQuotedValue(params.approvalToken)
|
|
@@ -72,10 +72,7 @@ async function verifyMutatingApprovalForWorkstream(
|
|
|
72
72
|
return { ok: false, reason: 'approvalMessageId cannot be empty when provided.' }
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const recentMessages = await
|
|
76
|
-
workstreamId,
|
|
77
|
-
APPROVAL_VERIFICATION_MESSAGE_WINDOW,
|
|
78
|
-
)
|
|
75
|
+
const recentMessages = await threadMessageService.listRecentMessages(threadId, APPROVAL_VERIFICATION_MESSAGE_WINDOW)
|
|
79
76
|
const userMessages = recentMessages.filter((message) => message.role === 'user')
|
|
80
77
|
if (userMessages.length === 0) {
|
|
81
78
|
return { ok: false, reason: 'No user message history available to verify mutating approval.' }
|
|
@@ -106,5 +103,5 @@ async function verifyMutatingApprovalForWorkstream(
|
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
export const verifyMutatingApproval: VerifyMutatingApproval = async (params) => {
|
|
109
|
-
return
|
|
106
|
+
return verifyMutatingApprovalForThread(ensureRecordId(params.threadId, TABLES.THREAD), params)
|
|
110
107
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
NodeResultQuality,
|
|
3
2
|
PlanArtifactSubmission,
|
|
4
3
|
PlanNodeResult,
|
|
5
4
|
PlanNodeSpec,
|
|
@@ -86,7 +85,7 @@ class NodeWorkspaceService {
|
|
|
86
85
|
})
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
finalize(workspace: NodeWorkspace): PlanNodeResult & { isComplete: boolean
|
|
88
|
+
finalize(workspace: NodeWorkspace): PlanNodeResult & { isComplete: boolean } {
|
|
90
89
|
const artifacts: PlanArtifactSubmission[] = []
|
|
91
90
|
let structuredOutput: Record<string, unknown> | undefined
|
|
92
91
|
let allValidated = true
|
|
@@ -125,8 +124,6 @@ class NodeWorkspaceService {
|
|
|
125
124
|
artifacts.push({
|
|
126
125
|
name: spec.name,
|
|
127
126
|
kind: spec.kind,
|
|
128
|
-
pointer: `workspace://${workspace.nodeId}/${targetPath}`,
|
|
129
|
-
schemaRef: spec.schemaRef,
|
|
130
127
|
description: spec.description,
|
|
131
128
|
payload:
|
|
132
129
|
typeof entry.payload === 'string'
|
|
@@ -138,10 +135,14 @@ class NodeWorkspaceService {
|
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
const allRequiredPresent = requiredNames.size === presentRequired.size
|
|
141
|
-
const
|
|
142
|
-
const isComplete = quality === 'full'
|
|
138
|
+
const isComplete = allRequiredPresent && allValidated
|
|
143
139
|
|
|
144
|
-
return {
|
|
140
|
+
return {
|
|
141
|
+
notes: isComplete ? 'All deliverables completed.' : 'Partial deliverables completed.',
|
|
142
|
+
structuredOutput,
|
|
143
|
+
artifacts,
|
|
144
|
+
isComplete,
|
|
145
|
+
}
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
cleanup(workspace: NodeWorkspace): void {
|
|
@@ -11,8 +11,8 @@ import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-
|
|
|
11
11
|
const organizationRecordSchema = z.object({
|
|
12
12
|
id: recordIdSchema,
|
|
13
13
|
name: z.string(),
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
regularChatDigestLastThreadCursorCreatedAt: z.coerce.date().optional(),
|
|
15
|
+
regularChatDigestLastThreadCursorId: z.string().optional(),
|
|
16
16
|
skillExtractionLastCursorId: z.string().optional(),
|
|
17
17
|
skillExtractionLastCursorCreatedAt: z.coerce.date().optional(),
|
|
18
18
|
createdAt: z.coerce.date(),
|
|
@@ -22,8 +22,8 @@ const organizationRecordSchema = z.object({
|
|
|
22
22
|
const sdkOrganizationSchema = z.object({
|
|
23
23
|
id: recordIdStringSchema,
|
|
24
24
|
name: z.string(),
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
regularChatDigestLastThreadCursorCreatedAt: z.iso.datetime().nullable().optional(),
|
|
26
|
+
regularChatDigestLastThreadCursorId: z.string().nullable().optional(),
|
|
27
27
|
skillExtractionLastCursorId: z.string().nullable().optional(),
|
|
28
28
|
skillExtractionLastCursorCreatedAt: z.iso.datetime().nullable().optional(),
|
|
29
29
|
createdAt: z.iso.datetime(),
|
|
@@ -51,10 +51,10 @@ class OrganizationService extends BaseService<typeof organizationRecordSchema> {
|
|
|
51
51
|
return sdkOrganizationSchema.parse({
|
|
52
52
|
id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.ORGANIZATION), TABLES.ORGANIZATION),
|
|
53
53
|
name: record.name,
|
|
54
|
-
|
|
55
|
-
record.
|
|
54
|
+
regularChatDigestLastThreadCursorCreatedAt: toOptionalCursorTimestamp(
|
|
55
|
+
record.regularChatDigestLastThreadCursorCreatedAt,
|
|
56
56
|
),
|
|
57
|
-
|
|
57
|
+
regularChatDigestLastThreadCursorId: record.regularChatDigestLastThreadCursorId ?? null,
|
|
58
58
|
skillExtractionLastCursorId: record.skillExtractionLastCursorId ?? null,
|
|
59
59
|
skillExtractionLastCursorCreatedAt: toOptionalCursorTimestamp(record.skillExtractionLastCursorCreatedAt),
|
|
60
60
|
createdAt: toIsoDateTimeString(record.createdAt),
|
|
@@ -101,8 +101,8 @@ class OrganizationService extends BaseService<typeof organizationRecordSchema> {
|
|
|
101
101
|
|
|
102
102
|
async updateRegularChatDigestCursor(organizationId: RecordIdRef, cursor: BackgroundCursor): Promise<void> {
|
|
103
103
|
await this.update(organizationId, {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
regularChatDigestLastThreadCursorCreatedAt: cursor.createdAt,
|
|
105
|
+
regularChatDigestLastThreadCursorId: cursor.id,
|
|
106
106
|
})
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -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,
|