@lota-sdk/core 0.1.8 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/schema/00_workstream.surql +2 -1
- package/infrastructure/schema/02_execution_plan.surql +202 -52
- package/package.json +4 -2
- package/src/bifrost/bifrost.ts +94 -25
- package/src/config/model-constants.ts +8 -6
- package/src/db/memory-store.ts +3 -71
- package/src/db/service.ts +42 -2
- package/src/db/tables.ts +9 -2
- package/src/embeddings/provider.ts +92 -21
- package/src/index.ts +6 -0
- package/src/redis/stream-context.ts +44 -0
- package/src/runtime/approval-continuation.ts +59 -0
- package/src/runtime/chat-request-routing.ts +5 -1
- package/src/runtime/execution-plan.ts +21 -14
- package/src/runtime/turn-lifecycle.ts +14 -6
- package/src/runtime/workstream-chat-helpers.ts +5 -5
- package/src/services/context-compaction.service.ts +6 -2
- package/src/services/document-chunk.service.ts +2 -2
- package/src/services/execution-plan.service.ts +579 -786
- package/src/services/learned-skill.service.ts +2 -2
- package/src/services/plan-approval.service.ts +83 -0
- package/src/services/plan-artifact.service.ts +45 -0
- package/src/services/plan-builder.service.ts +61 -0
- package/src/services/plan-checkpoint.service.ts +53 -0
- package/src/services/plan-compiler.service.ts +81 -0
- package/src/services/plan-executor.service.ts +1623 -0
- package/src/services/plan-run.service.ts +422 -0
- package/src/services/plan-validator.service.ts +760 -0
- package/src/services/workstream-turn-preparation.ts +70 -196
- package/src/services/workstream-turn.ts +12 -0
- package/src/services/workstream.service.ts +24 -182
- package/src/services/workstream.types.ts +2 -65
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +20 -46
- package/src/tools/log-hello-world.tool.ts +17 -0
- package/src/workers/skill-extraction.runner.ts +2 -2
- package/src/services/workstream-change-tracker.service.ts +0 -313
- package/src/system-agents/workstream-tracker.agent.ts +0 -58
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlanApprovalSchema,
|
|
3
|
+
PlanArtifactSchema,
|
|
4
|
+
PlanCheckpointSchema,
|
|
5
|
+
PlanEventSchema,
|
|
6
|
+
PlanNodeAttemptSchema,
|
|
7
|
+
PlanNodeRunSchema,
|
|
8
|
+
PlanNodeSpecRecordSchema,
|
|
9
|
+
PlanRunSchema,
|
|
10
|
+
PlanSpecSchema,
|
|
11
|
+
PlanValidationIssueSchema,
|
|
12
|
+
} from '@lota-sdk/shared/schemas/execution-plan'
|
|
13
|
+
import type {
|
|
14
|
+
PlanApprovalRecord,
|
|
15
|
+
PlanArtifactRecord,
|
|
16
|
+
PlanCheckpointRecord,
|
|
17
|
+
PlanEventRecord,
|
|
18
|
+
PlanNodeAttemptRecord,
|
|
19
|
+
PlanNodeRunRecord,
|
|
20
|
+
PlanNodeRunStatus,
|
|
21
|
+
PlanNodeSpecRecord,
|
|
22
|
+
PlanRunRecord,
|
|
23
|
+
PlanSpecRecord,
|
|
24
|
+
PlanValidationIssueRecord,
|
|
25
|
+
SerializableExecutionPlan,
|
|
26
|
+
SerializablePlanApproval,
|
|
27
|
+
SerializablePlanArtifact,
|
|
28
|
+
SerializablePlanCheckpoint,
|
|
29
|
+
SerializablePlanEvent,
|
|
30
|
+
SerializablePlanNode,
|
|
31
|
+
SerializablePlanValidationIssue,
|
|
32
|
+
} from '@lota-sdk/shared/schemas/execution-plan'
|
|
33
|
+
|
|
34
|
+
import type { RecordIdInput } from '../db/record-id'
|
|
35
|
+
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
36
|
+
import { databaseService } from '../db/service'
|
|
37
|
+
import { TABLES } from '../db/tables'
|
|
38
|
+
import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
|
|
39
|
+
|
|
40
|
+
const ACTIVE_RUN_STATUSES = new Set(['running', 'awaiting-human', 'blocked'])
|
|
41
|
+
|
|
42
|
+
function buildProgress(nodeRuns: PlanNodeRunRecord[]) {
|
|
43
|
+
const counts = nodeRuns.reduce(
|
|
44
|
+
(summary, nodeRun) => {
|
|
45
|
+
summary[nodeRun.status] += 1
|
|
46
|
+
return summary
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pending: 0,
|
|
50
|
+
ready: 0,
|
|
51
|
+
running: 0,
|
|
52
|
+
'awaiting-human': 0,
|
|
53
|
+
completed: 0,
|
|
54
|
+
partial: 0,
|
|
55
|
+
blocked: 0,
|
|
56
|
+
failed: 0,
|
|
57
|
+
skipped: 0,
|
|
58
|
+
} satisfies Record<PlanNodeRunStatus, number>,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const total = nodeRuns.length
|
|
62
|
+
const completedWork = counts.completed + counts.partial + counts.skipped
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
total,
|
|
66
|
+
pending: counts.pending,
|
|
67
|
+
ready: counts.ready,
|
|
68
|
+
running: counts.running,
|
|
69
|
+
awaitingHuman: counts['awaiting-human'],
|
|
70
|
+
completed: counts.completed,
|
|
71
|
+
partial: counts.partial,
|
|
72
|
+
blocked: counts.blocked,
|
|
73
|
+
failed: counts.failed,
|
|
74
|
+
skipped: counts.skipped,
|
|
75
|
+
completionRatio: total > 0 ? Number((completedWork / total).toFixed(4)) : undefined,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function serializeArtifact(artifact: PlanArtifactRecord): SerializablePlanArtifact {
|
|
80
|
+
return {
|
|
81
|
+
id: recordIdToString(artifact.id, TABLES.PLAN_ARTIFACT),
|
|
82
|
+
nodeId: artifact.nodeId,
|
|
83
|
+
attemptId: recordIdToString(artifact.attemptId, TABLES.PLAN_NODE_ATTEMPT),
|
|
84
|
+
name: artifact.name,
|
|
85
|
+
kind: artifact.kind,
|
|
86
|
+
pointer: artifact.pointer,
|
|
87
|
+
schemaRef: artifact.schemaRef,
|
|
88
|
+
description: artifact.description,
|
|
89
|
+
payload: artifact.payload,
|
|
90
|
+
createdAt: toIsoDateTimeString(artifact.createdAt),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function serializeValidationIssue(issue: PlanValidationIssueRecord): SerializablePlanValidationIssue {
|
|
95
|
+
return {
|
|
96
|
+
id: recordIdToString(issue.id, TABLES.PLAN_VALIDATION_ISSUE),
|
|
97
|
+
nodeId: issue.nodeId,
|
|
98
|
+
attemptId: issue.attemptId ? recordIdToString(issue.attemptId, TABLES.PLAN_NODE_ATTEMPT) : undefined,
|
|
99
|
+
severity: issue.severity,
|
|
100
|
+
code: issue.code,
|
|
101
|
+
message: issue.message,
|
|
102
|
+
detail: issue.detail,
|
|
103
|
+
createdAt: toIsoDateTimeString(issue.createdAt),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function serializeApproval(approval: PlanApprovalRecord): SerializablePlanApproval {
|
|
108
|
+
return {
|
|
109
|
+
id: recordIdToString(approval.id, TABLES.PLAN_APPROVAL),
|
|
110
|
+
nodeId: approval.nodeId,
|
|
111
|
+
status: approval.status,
|
|
112
|
+
presented: approval.presented,
|
|
113
|
+
response: approval.response,
|
|
114
|
+
requestedBy: approval.requestedBy,
|
|
115
|
+
respondedBy: approval.respondedBy,
|
|
116
|
+
approvalMessageId: approval.approvalMessageId,
|
|
117
|
+
comments: approval.comments,
|
|
118
|
+
requiredEdits: [...approval.requiredEdits],
|
|
119
|
+
createdAt: toIsoDateTimeString(approval.createdAt),
|
|
120
|
+
respondedAt: toOptionalIsoDateTimeString(approval.respondedAt),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function serializeCheckpoint(checkpoint: PlanCheckpointRecord): SerializablePlanCheckpoint {
|
|
125
|
+
return {
|
|
126
|
+
id: recordIdToString(checkpoint.id, TABLES.PLAN_CHECKPOINT),
|
|
127
|
+
sequence: checkpoint.sequence,
|
|
128
|
+
runStatus: checkpoint.runStatus,
|
|
129
|
+
readyNodeIds: [...checkpoint.readyNodeIds],
|
|
130
|
+
activeNodeIds: [...checkpoint.activeNodeIds],
|
|
131
|
+
artifactIds: checkpoint.artifactIds.map((artifactId) => recordIdToString(artifactId, TABLES.PLAN_ARTIFACT)),
|
|
132
|
+
lastCompletedNodeIds: [...checkpoint.lastCompletedNodeIds],
|
|
133
|
+
snapshot: checkpoint.snapshot,
|
|
134
|
+
createdAt: toIsoDateTimeString(checkpoint.createdAt),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function serializeEvent(event: PlanEventRecord): SerializablePlanEvent {
|
|
139
|
+
return {
|
|
140
|
+
id: recordIdToString(event.id, TABLES.PLAN_EVENT),
|
|
141
|
+
nodeId: event.nodeId,
|
|
142
|
+
attemptId: event.attemptId ? recordIdToString(event.attemptId, TABLES.PLAN_NODE_ATTEMPT) : undefined,
|
|
143
|
+
approvalId: event.approvalId ? recordIdToString(event.approvalId, TABLES.PLAN_APPROVAL) : undefined,
|
|
144
|
+
eventType: event.eventType,
|
|
145
|
+
fromStatus: event.fromStatus,
|
|
146
|
+
toStatus: event.toStatus,
|
|
147
|
+
message: event.message,
|
|
148
|
+
detail: event.detail,
|
|
149
|
+
emittedBy: event.emittedBy,
|
|
150
|
+
createdAt: toIsoDateTimeString(event.createdAt),
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class PlanRunService {
|
|
155
|
+
async getPlanSpecById(planSpecId: RecordIdInput): Promise<PlanSpecRecord> {
|
|
156
|
+
const spec = await databaseService.findOne(
|
|
157
|
+
TABLES.PLAN_SPEC,
|
|
158
|
+
{ id: ensureRecordId(planSpecId, TABLES.PLAN_SPEC) },
|
|
159
|
+
PlanSpecSchema,
|
|
160
|
+
)
|
|
161
|
+
if (!spec) {
|
|
162
|
+
throw new Error(`Plan spec not found: ${recordIdToString(planSpecId, TABLES.PLAN_SPEC)}`)
|
|
163
|
+
}
|
|
164
|
+
return spec
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async listNodeSpecs(planSpecId: RecordIdInput): Promise<PlanNodeSpecRecord[]> {
|
|
168
|
+
return await databaseService.findMany(
|
|
169
|
+
TABLES.PLAN_NODE_SPEC,
|
|
170
|
+
{ planSpecId: ensureRecordId(planSpecId, TABLES.PLAN_SPEC) },
|
|
171
|
+
PlanNodeSpecRecordSchema,
|
|
172
|
+
{ orderBy: 'position', orderDir: 'ASC' },
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async getNodeSpecByNodeId(planSpecId: RecordIdInput, nodeId: string): Promise<PlanNodeSpecRecord> {
|
|
177
|
+
const nodeSpec = await databaseService.findOne(
|
|
178
|
+
TABLES.PLAN_NODE_SPEC,
|
|
179
|
+
{ planSpecId: ensureRecordId(planSpecId, TABLES.PLAN_SPEC), nodeId },
|
|
180
|
+
PlanNodeSpecRecordSchema,
|
|
181
|
+
)
|
|
182
|
+
if (!nodeSpec) {
|
|
183
|
+
throw new Error(`Plan node spec "${nodeId}" not found.`)
|
|
184
|
+
}
|
|
185
|
+
return nodeSpec
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getRunById(runId: RecordIdInput): Promise<PlanRunRecord> {
|
|
189
|
+
const run = await databaseService.findOne(
|
|
190
|
+
TABLES.PLAN_RUN,
|
|
191
|
+
{ id: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
192
|
+
PlanRunSchema,
|
|
193
|
+
)
|
|
194
|
+
if (!run) {
|
|
195
|
+
throw new Error(`Plan run not found: ${recordIdToString(runId, TABLES.PLAN_RUN)}`)
|
|
196
|
+
}
|
|
197
|
+
return run
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async getActiveRunRecord(workstreamId: RecordIdInput): Promise<PlanRunRecord | null> {
|
|
201
|
+
const runs = await databaseService.findMany(
|
|
202
|
+
TABLES.PLAN_RUN,
|
|
203
|
+
{ workstreamId: ensureRecordId(workstreamId, TABLES.WORKSTREAM) },
|
|
204
|
+
PlanRunSchema,
|
|
205
|
+
{ orderBy: 'updatedAt', orderDir: 'DESC' },
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return runs.find((run) => ACTIVE_RUN_STATUSES.has(run.status)) ?? null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async listNodeRuns(runId: RecordIdInput): Promise<PlanNodeRunRecord[]> {
|
|
212
|
+
return await databaseService.findMany(
|
|
213
|
+
TABLES.PLAN_NODE_RUN,
|
|
214
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
215
|
+
PlanNodeRunSchema,
|
|
216
|
+
{ orderBy: 'nodeId', orderDir: 'ASC' },
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async getNodeRunByNodeId(runId: RecordIdInput, nodeId: string): Promise<PlanNodeRunRecord> {
|
|
221
|
+
const nodeRun = await databaseService.findOne(
|
|
222
|
+
TABLES.PLAN_NODE_RUN,
|
|
223
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN), nodeId },
|
|
224
|
+
PlanNodeRunSchema,
|
|
225
|
+
)
|
|
226
|
+
if (!nodeRun) {
|
|
227
|
+
throw new Error(`Plan node run "${nodeId}" not found.`)
|
|
228
|
+
}
|
|
229
|
+
return nodeRun
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async listArtifacts(runId: RecordIdInput): Promise<PlanArtifactRecord[]> {
|
|
233
|
+
return await databaseService.findMany(
|
|
234
|
+
TABLES.PLAN_ARTIFACT,
|
|
235
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
236
|
+
PlanArtifactSchema,
|
|
237
|
+
{ orderBy: 'createdAt', orderDir: 'ASC' },
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async listAttempts(runId: RecordIdInput): Promise<PlanNodeAttemptRecord[]> {
|
|
242
|
+
return await databaseService.findMany(
|
|
243
|
+
TABLES.PLAN_NODE_ATTEMPT,
|
|
244
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
245
|
+
PlanNodeAttemptSchema,
|
|
246
|
+
{ orderBy: 'createdAt', orderDir: 'ASC' },
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async listValidationIssues(params: {
|
|
251
|
+
runId?: RecordIdInput
|
|
252
|
+
planSpecId?: RecordIdInput
|
|
253
|
+
attemptId?: RecordIdInput
|
|
254
|
+
}): Promise<PlanValidationIssueRecord[]> {
|
|
255
|
+
const filter: Record<string, unknown> = {}
|
|
256
|
+
if (params.runId) filter.runId = ensureRecordId(params.runId, TABLES.PLAN_RUN)
|
|
257
|
+
if (params.planSpecId) filter.planSpecId = ensureRecordId(params.planSpecId, TABLES.PLAN_SPEC)
|
|
258
|
+
if (params.attemptId) filter.attemptId = ensureRecordId(params.attemptId, TABLES.PLAN_NODE_ATTEMPT)
|
|
259
|
+
|
|
260
|
+
return await databaseService.findMany(TABLES.PLAN_VALIDATION_ISSUE, filter, PlanValidationIssueSchema, {
|
|
261
|
+
orderBy: 'createdAt',
|
|
262
|
+
orderDir: 'ASC',
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async listApprovals(runId: RecordIdInput): Promise<PlanApprovalRecord[]> {
|
|
267
|
+
return await databaseService.findMany(
|
|
268
|
+
TABLES.PLAN_APPROVAL,
|
|
269
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
270
|
+
PlanApprovalSchema,
|
|
271
|
+
{ orderBy: 'createdAt', orderDir: 'ASC' },
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async getLatestCheckpoint(runId: RecordIdInput): Promise<PlanCheckpointRecord | null> {
|
|
276
|
+
const checkpoints = await databaseService.findMany(
|
|
277
|
+
TABLES.PLAN_CHECKPOINT,
|
|
278
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
279
|
+
PlanCheckpointSchema,
|
|
280
|
+
{ orderBy: 'sequence', orderDir: 'DESC', limit: 1 },
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return checkpoints.at(0) ?? null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async getNextCheckpointSequence(runId: RecordIdInput): Promise<number> {
|
|
287
|
+
const latestCheckpoint = await this.getLatestCheckpoint(runId)
|
|
288
|
+
return latestCheckpoint ? latestCheckpoint.sequence + 1 : 1
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async listEvents(runId: RecordIdInput, limit = 20): Promise<PlanEventRecord[]> {
|
|
292
|
+
return (
|
|
293
|
+
await databaseService.findMany(
|
|
294
|
+
TABLES.PLAN_EVENT,
|
|
295
|
+
{ runId: ensureRecordId(runId, TABLES.PLAN_RUN) },
|
|
296
|
+
PlanEventSchema,
|
|
297
|
+
{ orderBy: 'createdAt', orderDir: 'DESC', limit },
|
|
298
|
+
)
|
|
299
|
+
).reverse()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async toSerializablePlan(
|
|
303
|
+
run: PlanRunRecord,
|
|
304
|
+
options?: {
|
|
305
|
+
includeArtifacts?: boolean
|
|
306
|
+
includeApprovals?: boolean
|
|
307
|
+
includeCheckpoints?: boolean
|
|
308
|
+
includeEvents?: boolean
|
|
309
|
+
includeValidationIssues?: boolean
|
|
310
|
+
},
|
|
311
|
+
): Promise<SerializableExecutionPlan> {
|
|
312
|
+
const spec = await this.getPlanSpecById(run.planSpecId)
|
|
313
|
+
const nodeSpecs = await this.listNodeSpecs(spec.id)
|
|
314
|
+
const nodeRuns = await this.listNodeRuns(run.id)
|
|
315
|
+
const artifacts = options?.includeArtifacts === false ? [] : await this.listArtifacts(run.id)
|
|
316
|
+
const lineageArtifacts = options?.includeArtifacts === false ? [] : await this.collectLineageArtifacts(run)
|
|
317
|
+
const approvals = options?.includeApprovals === false ? [] : await this.listApprovals(run.id)
|
|
318
|
+
const validationIssues =
|
|
319
|
+
options?.includeValidationIssues === false
|
|
320
|
+
? []
|
|
321
|
+
: await this.listValidationIssues({ runId: run.id, planSpecId: spec.id })
|
|
322
|
+
const latestCheckpoint = options?.includeCheckpoints ? await this.getLatestCheckpoint(run.id) : null
|
|
323
|
+
const recentEvents = options?.includeEvents === false ? [] : await this.listEvents(run.id, 20)
|
|
324
|
+
const nodeRunsById = new Map(nodeRuns.map((nodeRun) => [nodeRun.nodeId, nodeRun]))
|
|
325
|
+
|
|
326
|
+
const nodes: SerializablePlanNode[] = nodeSpecs.map((nodeSpec) => {
|
|
327
|
+
const nodeRun = nodeRunsById.get(nodeSpec.nodeId)
|
|
328
|
+
if (!nodeRun) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Plan run ${recordIdToString(run.id, TABLES.PLAN_RUN)} is missing node run "${nodeSpec.nodeId}".`,
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
id: nodeSpec.nodeId,
|
|
336
|
+
type: nodeSpec.type,
|
|
337
|
+
label: nodeSpec.label,
|
|
338
|
+
owner: nodeSpec.owner,
|
|
339
|
+
objective: nodeSpec.objective,
|
|
340
|
+
instructions: nodeSpec.instructions,
|
|
341
|
+
inputSchemaRef: nodeSpec.inputSchemaRef,
|
|
342
|
+
outputSchemaRef: nodeSpec.outputSchemaRef,
|
|
343
|
+
deliverables: [...nodeSpec.deliverables],
|
|
344
|
+
successCriteria: [...nodeSpec.successCriteria],
|
|
345
|
+
completionChecks: [...nodeSpec.completionChecks],
|
|
346
|
+
retryPolicy: { ...nodeSpec.retryPolicy, retryOn: [...nodeSpec.retryPolicy.retryOn] },
|
|
347
|
+
failurePolicy: [...nodeSpec.failurePolicy],
|
|
348
|
+
timeoutMs: nodeSpec.timeoutMs,
|
|
349
|
+
toolPolicy: { allow: [...nodeSpec.toolPolicy.allow], deny: [...nodeSpec.toolPolicy.deny] },
|
|
350
|
+
contextPolicy: {
|
|
351
|
+
retrievalScopes: [...nodeSpec.contextPolicy.retrievalScopes],
|
|
352
|
+
attachmentPolicy: nodeSpec.contextPolicy.attachmentPolicy,
|
|
353
|
+
webPolicy: nodeSpec.contextPolicy.webPolicy,
|
|
354
|
+
},
|
|
355
|
+
status: nodeRun.status,
|
|
356
|
+
attemptCount: nodeRun.attemptCount,
|
|
357
|
+
retryCount: nodeRun.retryCount,
|
|
358
|
+
resolvedInput: nodeRun.resolvedInput,
|
|
359
|
+
latestStructuredOutput: nodeRun.latestStructuredOutput,
|
|
360
|
+
latestNotes: nodeRun.latestNotes,
|
|
361
|
+
blockedReason: nodeRun.blockedReason,
|
|
362
|
+
failureClass: nodeRun.failureClass,
|
|
363
|
+
upstreamNodeIds: [...nodeSpec.upstreamNodeIds],
|
|
364
|
+
downstreamNodeIds: [...nodeSpec.downstreamNodeIds],
|
|
365
|
+
readyAt: toOptionalIsoDateTimeString(nodeRun.readyAt),
|
|
366
|
+
startedAt: toOptionalIsoDateTimeString(nodeRun.startedAt),
|
|
367
|
+
completedAt: toOptionalIsoDateTimeString(nodeRun.completedAt),
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
specId: recordIdToString(spec.id, TABLES.PLAN_SPEC),
|
|
373
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
374
|
+
workstreamId: recordIdToString(run.workstreamId, TABLES.WORKSTREAM),
|
|
375
|
+
organizationId: recordIdToString(run.organizationId, TABLES.ORGANIZATION),
|
|
376
|
+
title: spec.title,
|
|
377
|
+
objective: spec.objective,
|
|
378
|
+
version: spec.version,
|
|
379
|
+
status: run.status,
|
|
380
|
+
leadAgentId: run.leadAgentId,
|
|
381
|
+
schemaRegistry: structuredClone(spec.schemaRegistry),
|
|
382
|
+
entryNodeIds: [...spec.entryNodeIds],
|
|
383
|
+
edges: [...spec.edges],
|
|
384
|
+
activeNodeIds: run.currentNodeId ? [run.currentNodeId] : [],
|
|
385
|
+
readyNodeIds: [...run.readyNodeIds],
|
|
386
|
+
waitingNodeId: run.waitingNodeId,
|
|
387
|
+
replacedRunId: run.replacedRunId ? recordIdToString(run.replacedRunId, TABLES.PLAN_RUN) : undefined,
|
|
388
|
+
failureCount: run.failureCount,
|
|
389
|
+
startedAt: toOptionalIsoDateTimeString(run.startedAt),
|
|
390
|
+
completedAt: toOptionalIsoDateTimeString(run.completedAt),
|
|
391
|
+
progress: buildProgress(nodeRuns),
|
|
392
|
+
nodes,
|
|
393
|
+
artifacts: artifacts.map(serializeArtifact),
|
|
394
|
+
lineageArtifacts,
|
|
395
|
+
validationIssues: validationIssues.map(serializeValidationIssue),
|
|
396
|
+
approvals: approvals.map(serializeApproval),
|
|
397
|
+
latestCheckpoint: latestCheckpoint ? serializeCheckpoint(latestCheckpoint) : null,
|
|
398
|
+
recentEvents: recentEvents.map(serializeEvent),
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private async collectLineageArtifacts(run: PlanRunRecord): Promise<SerializablePlanArtifact[]> {
|
|
403
|
+
const lineageArtifacts: SerializablePlanArtifact[] = []
|
|
404
|
+
let currentRunId = run.replacedRunId ? ensureRecordId(run.replacedRunId, TABLES.PLAN_RUN) : null
|
|
405
|
+
let depth = 0
|
|
406
|
+
|
|
407
|
+
while (currentRunId && depth < 5) {
|
|
408
|
+
const previousRun = await databaseService.findOne(TABLES.PLAN_RUN, { id: currentRunId }, PlanRunSchema)
|
|
409
|
+
if (!previousRun) break
|
|
410
|
+
|
|
411
|
+
const artifacts = await this.listArtifacts(previousRun.id)
|
|
412
|
+
lineageArtifacts.unshift(...artifacts.map(serializeArtifact))
|
|
413
|
+
|
|
414
|
+
currentRunId = previousRun.replacedRunId ? ensureRecordId(previousRun.replacedRunId, TABLES.PLAN_RUN) : null
|
|
415
|
+
depth += 1
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return lineageArtifacts
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export const planRunService = new PlanRunService()
|