@lota-sdk/core 0.4.0 → 0.4.2
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/02_execution_plan.surql +30 -0
- package/package.json +2 -2
- package/src/create-runtime.ts +29 -1
- package/src/db/tables.ts +1 -0
- package/src/queues/autonomous-job.queue.ts +31 -20
- package/src/queues/document-processor.queue.ts +13 -9
- package/src/queues/memory-consolidation.queue.ts +14 -9
- package/src/queues/queue-factory.ts +13 -9
- package/src/runtime/agent-runtime-policy.ts +2 -0
- package/src/runtime/thread-chat-helpers.ts +54 -0
- package/src/services/artifact.service.ts +371 -0
- package/src/services/autonomous-job.service.ts +16 -12
- package/src/services/execution-plan.service.ts +4 -13
- package/src/services/index.ts +1 -0
- package/src/services/ownership-dispatcher.service.ts +4 -0
- package/src/services/plan-artifact.service.ts +7 -1
- package/src/services/plan-coordination.service.ts +23 -18
- package/src/services/plan-executor.service.ts +360 -294
- package/src/services/plan-run.service.ts +4 -0
- package/src/services/plan-template.service.ts +57 -2
- package/src/services/plan-validator.service.ts +1 -1
- package/src/services/queue-job.service.ts +180 -134
- package/src/services/thread-turn-preparation.service.ts +36 -8
- package/src/services/thread-turn.ts +4 -0
- package/src/storage/generated-document-storage.service.ts +8 -0
- package/src/tools/execution-plan.tool.ts +43 -15
- package/src/workers/worker-utils.ts +10 -2
|
@@ -90,7 +90,11 @@ function serializeArtifact(artifact: PlanArtifactRecord): SerializablePlanArtifa
|
|
|
90
90
|
pointer: artifact.pointer,
|
|
91
91
|
schemaRef: artifact.schemaRef,
|
|
92
92
|
description: artifact.description,
|
|
93
|
+
content: artifact.content,
|
|
93
94
|
payload: artifact.payload,
|
|
95
|
+
publishedArtifactId: artifact.publishedArtifactId
|
|
96
|
+
? recordIdToString(artifact.publishedArtifactId, TABLES.ARTIFACT)
|
|
97
|
+
: undefined,
|
|
94
98
|
createdAt: toIsoDateTimeString(artifact.createdAt),
|
|
95
99
|
}
|
|
96
100
|
}
|
|
@@ -8,6 +8,17 @@ import { TABLES } from '../db/tables'
|
|
|
8
8
|
import { executionPlanService } from './execution-plan.service'
|
|
9
9
|
|
|
10
10
|
class PlanTemplateService {
|
|
11
|
+
private resolveSourceIdentity(params: { source?: 'user' | 'playbook' | 'system'; sourceRef?: string }) {
|
|
12
|
+
const source = params.source ?? 'user'
|
|
13
|
+
const sourceRef = params.sourceRef?.trim()
|
|
14
|
+
|
|
15
|
+
if (source !== 'user' && !sourceRef) {
|
|
16
|
+
throw new Error(`sourceRef is required when source is "${source}".`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { source, sourceRef }
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
async createTemplate(params: {
|
|
12
23
|
organizationId: RecordIdInput
|
|
13
24
|
name: string
|
|
@@ -18,6 +29,7 @@ class PlanTemplateService {
|
|
|
18
29
|
sourceRef?: string
|
|
19
30
|
}): Promise<PlanTemplateRecord> {
|
|
20
31
|
const now = new Date()
|
|
32
|
+
const identity = this.resolveSourceIdentity({ source: params.source, sourceRef: params.sourceRef })
|
|
21
33
|
return databaseService.create(
|
|
22
34
|
TABLES.PLAN_TEMPLATE,
|
|
23
35
|
{
|
|
@@ -26,8 +38,8 @@ class PlanTemplateService {
|
|
|
26
38
|
...(params.description ? { description: params.description } : {}),
|
|
27
39
|
draft: params.draft,
|
|
28
40
|
tags: params.tags ?? [],
|
|
29
|
-
source:
|
|
30
|
-
...(
|
|
41
|
+
source: identity.source,
|
|
42
|
+
...(identity.sourceRef ? { sourceRef: identity.sourceRef } : {}),
|
|
31
43
|
createdAt: now,
|
|
32
44
|
},
|
|
33
45
|
PlanTemplateRecordSchema,
|
|
@@ -42,6 +54,22 @@ class PlanTemplateService {
|
|
|
42
54
|
)
|
|
43
55
|
}
|
|
44
56
|
|
|
57
|
+
async getTemplateBySourceRef(params: {
|
|
58
|
+
organizationId: RecordIdInput
|
|
59
|
+
source: 'user' | 'playbook' | 'system'
|
|
60
|
+
sourceRef: string
|
|
61
|
+
}): Promise<PlanTemplateRecord | null> {
|
|
62
|
+
return await databaseService.findOne(
|
|
63
|
+
TABLES.PLAN_TEMPLATE,
|
|
64
|
+
{
|
|
65
|
+
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
66
|
+
source: params.source,
|
|
67
|
+
sourceRef: params.sourceRef,
|
|
68
|
+
},
|
|
69
|
+
PlanTemplateRecordSchema,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
45
73
|
async listTemplates(
|
|
46
74
|
organizationId: RecordIdInput,
|
|
47
75
|
params?: { tags?: string[]; source?: string },
|
|
@@ -80,6 +108,33 @@ class PlanTemplateService {
|
|
|
80
108
|
return updated
|
|
81
109
|
}
|
|
82
110
|
|
|
111
|
+
async upsertTemplateBySourceRef(params: {
|
|
112
|
+
organizationId: RecordIdInput
|
|
113
|
+
name: string
|
|
114
|
+
description?: string
|
|
115
|
+
draft: PlanDraft
|
|
116
|
+
tags?: string[]
|
|
117
|
+
source: 'user' | 'playbook' | 'system'
|
|
118
|
+
sourceRef: string
|
|
119
|
+
}): Promise<PlanTemplateRecord> {
|
|
120
|
+
const existing = await this.getTemplateBySourceRef({
|
|
121
|
+
organizationId: params.organizationId,
|
|
122
|
+
source: params.source,
|
|
123
|
+
sourceRef: params.sourceRef,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (!existing) {
|
|
127
|
+
return await this.createTemplate(params)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return await this.updateTemplate(existing.id, {
|
|
131
|
+
name: params.name,
|
|
132
|
+
description: params.description,
|
|
133
|
+
draft: params.draft,
|
|
134
|
+
tags: params.tags ?? [],
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
83
138
|
async deleteTemplate(templateId: RecordIdInput): Promise<void> {
|
|
84
139
|
await databaseService.deleteById(TABLES.PLAN_TEMPLATE, ensureRecordId(templateId, TABLES.PLAN_TEMPLATE))
|
|
85
140
|
}
|
|
@@ -510,7 +510,7 @@ class PlanValidatorService {
|
|
|
510
510
|
// Validate cross-plan dependency cycles
|
|
511
511
|
if (draft.dependencies && draft.dependencies.length > 0) {
|
|
512
512
|
const cycleIssues = planCoordinationService.validateNoCycles([
|
|
513
|
-
{ title: draft.title, dependencies: draft.dependencies },
|
|
513
|
+
{ id: draft.title, title: draft.title, dependencies: draft.dependencies },
|
|
514
514
|
])
|
|
515
515
|
blocking.push(...cycleIssues)
|
|
516
516
|
}
|
|
@@ -50,6 +50,10 @@ const QueueJobAttemptRowSchema = z.object({
|
|
|
50
50
|
updatedAt: z.coerce.date(),
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
+
const PERSISTENCE_MAX_ATTEMPTS = 4
|
|
54
|
+
const PERSISTENCE_RETRY_BASE_DELAY_MS = 25
|
|
55
|
+
const PERSISTENCE_RETRY_JITTER_MS = 25
|
|
56
|
+
|
|
53
57
|
export interface TrackedBullJobLike {
|
|
54
58
|
queueName: string
|
|
55
59
|
id?: string | number
|
|
@@ -174,7 +178,43 @@ function getQueuedStatus(job: TrackedBullJobLike): QueueJobStatus {
|
|
|
174
178
|
return typeof delay === 'number' && delay > 0 ? 'delayed' : 'waiting'
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
function isRetriablePersistenceConflict(error: unknown): boolean {
|
|
182
|
+
if (!(error instanceof Error)) return false
|
|
183
|
+
|
|
184
|
+
const message = error.message.toLowerCase()
|
|
185
|
+
return (
|
|
186
|
+
message.includes('transaction conflict') ||
|
|
187
|
+
message.includes('transaction read conflict') ||
|
|
188
|
+
message.includes('read or write conflict') ||
|
|
189
|
+
message.includes('write conflict') ||
|
|
190
|
+
message.includes('resource busy') ||
|
|
191
|
+
message.includes('this transaction can be retried')
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
class QueueJobService {
|
|
196
|
+
private async withPersistenceRetry<T>(work: () => Promise<T>): Promise<T> {
|
|
197
|
+
let lastError: unknown = null
|
|
198
|
+
|
|
199
|
+
for (let attempt = 1; attempt <= PERSISTENCE_MAX_ATTEMPTS; attempt += 1) {
|
|
200
|
+
try {
|
|
201
|
+
return await work()
|
|
202
|
+
} catch (error) {
|
|
203
|
+
lastError = error
|
|
204
|
+
const hasMoreAttempts = attempt < PERSISTENCE_MAX_ATTEMPTS
|
|
205
|
+
if (!isRetriablePersistenceConflict(error) || !hasMoreAttempts) {
|
|
206
|
+
throw error
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const backoffMs =
|
|
210
|
+
PERSISTENCE_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1) + Math.floor(Math.random() * PERSISTENCE_RETRY_JITTER_MS)
|
|
211
|
+
await Bun.sleep(backoffMs)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
throw lastError instanceof Error ? lastError : new Error('Queue job persistence retry exhausted')
|
|
216
|
+
}
|
|
217
|
+
|
|
178
218
|
getQueueJobId(queueName: string, bullmqJobId: string): string {
|
|
179
219
|
return recordIdToString(
|
|
180
220
|
buildDeterministicRecordId(TABLES.QUEUE_JOB, `${queueName}:${bullmqJobId}`),
|
|
@@ -184,172 +224,178 @@ class QueueJobService {
|
|
|
184
224
|
|
|
185
225
|
async recordEnqueued(job: TrackedBullJobLike, context?: Record<string, unknown>): Promise<string> {
|
|
186
226
|
await databaseService.connect()
|
|
227
|
+
return this.withPersistenceRetry(async () => {
|
|
228
|
+
const queueJobId = getQueueJobRecordId(job)
|
|
229
|
+
const queuedAt = typeof job.timestamp === 'number' ? new Date(job.timestamp) : new Date()
|
|
230
|
+
const mergedContext = compactRecord({ ...extractJobContext(job.data), ...context })
|
|
187
231
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
return recordIdToString(queueJobId, TABLES.QUEUE_JOB)
|
|
232
|
+
await databaseService.upsert(
|
|
233
|
+
TABLES.QUEUE_JOB,
|
|
234
|
+
queueJobId,
|
|
235
|
+
compactRecord({
|
|
236
|
+
queueName: job.queueName,
|
|
237
|
+
jobName: job.name,
|
|
238
|
+
bullmqJobId: getBullmqJobId(job),
|
|
239
|
+
status: getQueuedStatus(job),
|
|
240
|
+
data: wrapFlexibleValue(job.data),
|
|
241
|
+
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
242
|
+
context: Object.keys(mergedContext).length > 0 ? sanitizeQueueValue(mergedContext) : undefined,
|
|
243
|
+
deduplicationId: readDeduplicationId(job),
|
|
244
|
+
maxAttempts: readMaxAttempts(job),
|
|
245
|
+
queuedAt,
|
|
246
|
+
}),
|
|
247
|
+
QueueJobRowSchema,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return recordIdToString(queueJobId, TABLES.QUEUE_JOB)
|
|
251
|
+
})
|
|
211
252
|
}
|
|
212
253
|
|
|
213
254
|
async markAttemptStarted(job: TrackedBullJobLike): Promise<string> {
|
|
214
255
|
await databaseService.connect()
|
|
215
|
-
|
|
216
256
|
const attemptNumber = resolveAttemptNumber(job)
|
|
217
257
|
const queueJobId = getQueueJobRecordId(job)
|
|
218
258
|
const startedAt = new Date()
|
|
219
259
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
return this.withPersistenceRetry(async () => {
|
|
261
|
+
await databaseService.upsert(
|
|
262
|
+
TABLES.QUEUE_JOB,
|
|
263
|
+
queueJobId,
|
|
264
|
+
compactRecord({
|
|
265
|
+
queueName: job.queueName,
|
|
266
|
+
jobName: job.name,
|
|
267
|
+
bullmqJobId: getBullmqJobId(job),
|
|
268
|
+
status: 'active',
|
|
269
|
+
data: wrapFlexibleValue(job.data),
|
|
270
|
+
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
271
|
+
context: sanitizeQueueValue(extractJobContext(job.data)) as Record<string, unknown> | undefined,
|
|
272
|
+
deduplicationId: readDeduplicationId(job),
|
|
273
|
+
maxAttempts: readMaxAttempts(job),
|
|
274
|
+
attemptCount: attemptNumber,
|
|
275
|
+
queuedAt: typeof job.timestamp === 'number' ? new Date(job.timestamp) : startedAt,
|
|
276
|
+
startedAt,
|
|
277
|
+
}),
|
|
278
|
+
QueueJobRowSchema,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
const attemptId = getQueueJobAttemptRecordId(job, attemptNumber)
|
|
282
|
+
await databaseService.upsert(
|
|
283
|
+
TABLES.QUEUE_JOB_ATTEMPT,
|
|
284
|
+
attemptId,
|
|
285
|
+
{ queueJobId, attemptNumber, status: 'active', startedAt },
|
|
286
|
+
QueueJobAttemptRowSchema,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return recordIdToString(queueJobId, TABLES.QUEUE_JOB)
|
|
290
|
+
})
|
|
249
291
|
}
|
|
250
292
|
|
|
251
293
|
async markAttemptCompleted(job: TrackedBullJobLike, result: unknown): Promise<void> {
|
|
252
294
|
await databaseService.connect()
|
|
253
|
-
|
|
254
295
|
const attemptNumber = resolveAttemptNumber(job)
|
|
255
296
|
const queueJobId = getQueueJobRecordId(job)
|
|
256
297
|
const attemptId = getQueueJobAttemptRecordId(job, attemptNumber)
|
|
257
298
|
const completedAt = new Date()
|
|
258
|
-
const existingAttempt = await databaseService.findOne(
|
|
259
|
-
TABLES.QUEUE_JOB_ATTEMPT,
|
|
260
|
-
{ id: attemptId },
|
|
261
|
-
QueueJobAttemptRowSchema,
|
|
262
|
-
)
|
|
263
299
|
|
|
264
|
-
await
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
await this.withPersistenceRetry(async () => {
|
|
301
|
+
const existingAttempt = await databaseService.findOne(
|
|
302
|
+
TABLES.QUEUE_JOB_ATTEMPT,
|
|
303
|
+
{ id: attemptId },
|
|
304
|
+
QueueJobAttemptRowSchema,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
await databaseService.upsert(
|
|
308
|
+
TABLES.QUEUE_JOB_ATTEMPT,
|
|
309
|
+
attemptId,
|
|
310
|
+
compactRecord({
|
|
311
|
+
queueJobId,
|
|
312
|
+
attemptNumber,
|
|
313
|
+
status: 'completed',
|
|
314
|
+
result: wrapFlexibleValue(result),
|
|
315
|
+
startedAt: existingAttempt?.startedAt ?? completedAt,
|
|
316
|
+
completedAt,
|
|
317
|
+
durationMs: existingAttempt ? Math.max(0, completedAt.getTime() - existingAttempt.startedAt.getTime()) : 0,
|
|
318
|
+
}),
|
|
319
|
+
QueueJobAttemptRowSchema,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
await databaseService.upsert(
|
|
323
|
+
TABLES.QUEUE_JOB,
|
|
268
324
|
queueJobId,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
289
|
-
context: sanitizeQueueValue(extractJobContext(job.data)) as Record<string, unknown> | undefined,
|
|
290
|
-
deduplicationId: readDeduplicationId(job),
|
|
291
|
-
maxAttempts: readMaxAttempts(job),
|
|
292
|
-
attemptCount: attemptNumber,
|
|
293
|
-
queuedAt: typeof job.timestamp === 'number' ? new Date(job.timestamp) : completedAt,
|
|
294
|
-
completedAt,
|
|
295
|
-
result: wrapFlexibleValue(result),
|
|
296
|
-
lastError: undefined,
|
|
297
|
-
}),
|
|
298
|
-
QueueJobRowSchema,
|
|
299
|
-
)
|
|
325
|
+
compactRecord({
|
|
326
|
+
queueName: job.queueName,
|
|
327
|
+
jobName: job.name,
|
|
328
|
+
bullmqJobId: getBullmqJobId(job),
|
|
329
|
+
status: 'completed',
|
|
330
|
+
data: wrapFlexibleValue(job.data),
|
|
331
|
+
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
332
|
+
context: sanitizeQueueValue(extractJobContext(job.data)) as Record<string, unknown> | undefined,
|
|
333
|
+
deduplicationId: readDeduplicationId(job),
|
|
334
|
+
maxAttempts: readMaxAttempts(job),
|
|
335
|
+
attemptCount: attemptNumber,
|
|
336
|
+
queuedAt: typeof job.timestamp === 'number' ? new Date(job.timestamp) : completedAt,
|
|
337
|
+
completedAt,
|
|
338
|
+
result: wrapFlexibleValue(result),
|
|
339
|
+
lastError: undefined,
|
|
340
|
+
}),
|
|
341
|
+
QueueJobRowSchema,
|
|
342
|
+
)
|
|
343
|
+
})
|
|
300
344
|
}
|
|
301
345
|
|
|
302
346
|
async markAttemptFailed(job: TrackedBullJobLike, error: unknown): Promise<void> {
|
|
303
347
|
await databaseService.connect()
|
|
304
|
-
|
|
305
348
|
const attemptNumber = resolveAttemptNumber(job)
|
|
306
349
|
const queueJobId = getQueueJobRecordId(job)
|
|
307
350
|
const attemptId = getQueueJobAttemptRecordId(job, attemptNumber)
|
|
308
351
|
const failedAt = new Date()
|
|
309
|
-
const existingAttempt = await databaseService.findOne(
|
|
310
|
-
TABLES.QUEUE_JOB_ATTEMPT,
|
|
311
|
-
{ id: attemptId },
|
|
312
|
-
QueueJobAttemptRowSchema,
|
|
313
|
-
)
|
|
314
352
|
const normalizedError = toQueueJobError(error)
|
|
315
353
|
const maxAttempts = readMaxAttempts(job)
|
|
316
354
|
const terminal = typeof maxAttempts === 'number' ? attemptNumber >= maxAttempts : true
|
|
317
355
|
|
|
318
|
-
await
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
356
|
+
await this.withPersistenceRetry(async () => {
|
|
357
|
+
const existingAttempt = await databaseService.findOne(
|
|
358
|
+
TABLES.QUEUE_JOB_ATTEMPT,
|
|
359
|
+
{ id: attemptId },
|
|
360
|
+
QueueJobAttemptRowSchema,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
await databaseService.upsert(
|
|
364
|
+
TABLES.QUEUE_JOB_ATTEMPT,
|
|
365
|
+
attemptId,
|
|
366
|
+
compactRecord({
|
|
367
|
+
queueJobId,
|
|
368
|
+
attemptNumber,
|
|
369
|
+
status: 'failed',
|
|
370
|
+
error: normalizedError,
|
|
371
|
+
startedAt: existingAttempt?.startedAt ?? failedAt,
|
|
372
|
+
completedAt: failedAt,
|
|
373
|
+
durationMs: existingAttempt ? Math.max(0, failedAt.getTime() - existingAttempt.startedAt.getTime()) : 0,
|
|
374
|
+
}),
|
|
375
|
+
QueueJobAttemptRowSchema,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
await databaseService.upsert(
|
|
379
|
+
TABLES.QUEUE_JOB,
|
|
322
380
|
queueJobId,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
data: wrapFlexibleValue(job.data),
|
|
342
|
-
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
343
|
-
context: sanitizeQueueValue(extractJobContext(job.data)) as Record<string, unknown> | undefined,
|
|
344
|
-
deduplicationId: readDeduplicationId(job),
|
|
345
|
-
maxAttempts,
|
|
346
|
-
attemptCount: attemptNumber,
|
|
347
|
-
queuedAt: typeof job.timestamp === 'number' ? new Date(job.timestamp) : failedAt,
|
|
348
|
-
failedAt: terminal ? failedAt : undefined,
|
|
349
|
-
lastError: normalizedError,
|
|
350
|
-
}),
|
|
351
|
-
QueueJobRowSchema,
|
|
352
|
-
)
|
|
381
|
+
compactRecord({
|
|
382
|
+
queueName: job.queueName,
|
|
383
|
+
jobName: job.name,
|
|
384
|
+
bullmqJobId: getBullmqJobId(job),
|
|
385
|
+
status: terminal ? 'failed' : 'waiting',
|
|
386
|
+
data: wrapFlexibleValue(job.data),
|
|
387
|
+
options: sanitizeQueueValue(job.opts) as Record<string, unknown>,
|
|
388
|
+
context: sanitizeQueueValue(extractJobContext(job.data)) as Record<string, unknown> | undefined,
|
|
389
|
+
deduplicationId: readDeduplicationId(job),
|
|
390
|
+
maxAttempts,
|
|
391
|
+
attemptCount: attemptNumber,
|
|
392
|
+
queuedAt: typeof job.timestamp === 'number' ? new Date(job.timestamp) : failedAt,
|
|
393
|
+
failedAt: terminal ? failedAt : undefined,
|
|
394
|
+
lastError: normalizedError,
|
|
395
|
+
}),
|
|
396
|
+
QueueJobRowSchema,
|
|
397
|
+
)
|
|
398
|
+
})
|
|
353
399
|
}
|
|
354
400
|
}
|
|
355
401
|
|
|
@@ -41,6 +41,7 @@ import { runPostTurnSideEffects } from '../runtime/post-turn-side-effects'
|
|
|
41
41
|
import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/runtime-extensions'
|
|
42
42
|
import {
|
|
43
43
|
asRecord,
|
|
44
|
+
collectCompletedConsultTeamMessages,
|
|
44
45
|
collectToolOutputErrors,
|
|
45
46
|
extractMessageText,
|
|
46
47
|
readInstructionSections,
|
|
@@ -767,17 +768,44 @@ export async function prepareThreadRunCore(params: ThreadRunCoreParams): Promise
|
|
|
767
768
|
) => {
|
|
768
769
|
throwIfRunAborted()
|
|
769
770
|
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
771
|
+
const toCommittedAssistantMessage = (
|
|
772
|
+
message: ChatMessage,
|
|
773
|
+
resolvedAgentId: string,
|
|
774
|
+
resolvedAgentName: string,
|
|
775
|
+
patch?: NonNullable<MessageMetadata>,
|
|
776
|
+
) =>
|
|
777
|
+
withMessageCreatedAt(
|
|
778
|
+
{
|
|
779
|
+
...message,
|
|
780
|
+
metadata: {
|
|
781
|
+
...message.metadata,
|
|
782
|
+
...buildAgentMetadataPatch(resolvedAgentId, resolvedAgentName),
|
|
783
|
+
...patch,
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
toTimestamp(message.metadata?.createdAt) ?? Date.now(),
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
const committedConsultMessages = collectCompletedConsultTeamMessages({ responseMessage: response }).flatMap(
|
|
790
|
+
(consultMessage) => {
|
|
791
|
+
const consultAgentId = readOptionalString(consultMessage.metadata?.agentId)
|
|
792
|
+
const consultAgentName = readOptionalString(consultMessage.metadata?.agentName)
|
|
793
|
+
if (!consultAgentId || !consultAgentName) {
|
|
794
|
+
return []
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return [toCommittedAssistantMessage(consultMessage, consultAgentId, consultAgentName)]
|
|
774
798
|
},
|
|
775
|
-
Date.now(),
|
|
776
799
|
)
|
|
777
800
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
801
|
+
const committed = toCommittedAssistantMessage(response, agentId, agentName, metadataPatch)
|
|
802
|
+
const messagesToPersist = [...committedConsultMessages, committed]
|
|
803
|
+
|
|
804
|
+
await threadMessageService.upsertMessages({ threadId: threadRef, messages: messagesToPersist })
|
|
805
|
+
for (const persistedMessage of messagesToPersist) {
|
|
806
|
+
currentMessages = upsertChatHistoryMessage(currentMessages, persistedMessage)
|
|
807
|
+
allAssistantMessages = upsertChatHistoryMessage(allAssistantMessages, persistedMessage)
|
|
808
|
+
}
|
|
781
809
|
throwIfRunAborted()
|
|
782
810
|
|
|
783
811
|
return committed
|
|
@@ -121,7 +121,11 @@ export async function triggerPlanNodeTurn(params: {
|
|
|
121
121
|
name: artifact.name,
|
|
122
122
|
kind: artifact.kind,
|
|
123
123
|
...(artifact.description ? { description: artifact.description } : {}),
|
|
124
|
+
...(artifact.content !== undefined ? { content: artifact.content } : {}),
|
|
124
125
|
...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
|
|
126
|
+
...(artifact.publishedArtifactId
|
|
127
|
+
? { publishedArtifactId: recordIdToString(artifact.publishedArtifactId, TABLES.ARTIFACT) }
|
|
128
|
+
: {}),
|
|
125
129
|
}))
|
|
126
130
|
const upstreamNodeSpecs = new Map(
|
|
127
131
|
(await planRunService.listNodeSpecs(spec.id)).map((upstreamNodeSpec) => [
|
|
@@ -52,6 +52,14 @@ class GeneratedDocumentStorageService {
|
|
|
52
52
|
await this.client.file(storageKey).write(params.content, { type: params.mediaType })
|
|
53
53
|
return { storageKey, sizeBytes }
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
async readTextArtifact(storageKey: string): Promise<string> {
|
|
57
|
+
return await this.client.file(storageKey).text()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async deleteTextArtifact(storageKey: string): Promise<void> {
|
|
61
|
+
await this.client.file(storageKey).delete()
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
export const generatedDocumentStorageService = new GeneratedDocumentStorageService()
|