@lota-sdk/core 0.1.22 → 0.1.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "@chat-adapter/slack": "^4.23.0",
33
33
  "@chat-adapter/state-ioredis": "^4.23.0",
34
34
  "@logtape/logtape": "^2.0.5",
35
- "@lota-sdk/shared": "0.1.22",
35
+ "@lota-sdk/shared": "0.1.24",
36
36
  "@mendable/firecrawl-js": "^4.18.0",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.141",
@@ -376,6 +376,10 @@ function createAiGatewayProvider(extraParams?: AiGatewayExtraParams) {
376
376
  }
377
377
 
378
378
  function withAiGatewayDevTools<TModel extends AiGatewayLanguageModel>(model: TModel): TModel {
379
+ if (process.env.NODE_ENV === 'production') {
380
+ return model
381
+ }
382
+
379
383
  return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
380
384
  }
381
385
 
@@ -27,6 +27,8 @@ import type { LotaRuntimeWorkers } from './runtime/runtime-worker-registry'
27
27
  import { buildRuntimeWorkerRegistry } from './runtime/runtime-worker-registry'
28
28
  import type { LotaRuntimeSocialChat } from './runtime/social-chat'
29
29
  import { createSocialChatRuntime } from './runtime/social-chat'
30
+ import type { agentActivityService } from './services/agent-activity.service'
31
+ import { agentActivityService as agentActivityServiceSingleton } from './services/agent-activity.service'
30
32
  import type { attachmentService } from './services/attachment.service'
31
33
  import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
32
34
  import type { autonomousJobService } from './services/autonomous-job.service'
@@ -117,6 +119,7 @@ export interface LotaRuntime {
117
119
  database: SurrealDBService
118
120
  redis: RedisConnectionManager
119
121
  closeRedisConnection: () => Promise<void>
122
+ agentActivityService: typeof agentActivityService
120
123
  attachmentService: typeof attachmentService
121
124
  autonomousJobService: typeof autonomousJobService
122
125
  documentChunkService: typeof documentChunkService
@@ -390,6 +393,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
390
393
  database: db,
391
394
  redis: redisManager,
392
395
  closeRedisConnection: async () => await redisManager.closeConnection(),
396
+ agentActivityService: agentActivityServiceSingleton,
393
397
  attachmentService: attachmentServiceSingleton,
394
398
  autonomousJobService: autonomousJobServiceSingleton,
395
399
  documentChunkService: documentChunkServiceSingleton,
@@ -1,3 +1,4 @@
1
+ import { PROJECT_PLAN_ROUTING_PROMPT } from '@lota-sdk/shared'
1
2
  import type { SerializableExecutionPlan } from '@lota-sdk/shared'
2
3
 
3
4
  const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
@@ -35,7 +36,7 @@ export function buildExecutionPlanInstructionSections(
35
36
  plans: SerializableExecutionPlan[] | null | undefined,
36
37
  ): string[] | undefined {
37
38
  const normalized = plans ?? []
38
- const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
39
+ const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT, PROJECT_PLAN_ROUTING_PROMPT]
39
40
  const stateSection = formatExecutionPlansForPrompt(normalized)
40
41
  if (stateSection) {
41
42
  sections.push(stateSection)
@@ -0,0 +1,350 @@
1
+ import type {
2
+ AgentActivityCounts,
3
+ AgentActivityEntry,
4
+ AgentActivityResponse,
5
+ AgentProjectEntry,
6
+ MyTasksResponse,
7
+ PlanBoardColumn,
8
+ PlanBoardResponse,
9
+ PlanNodeCard,
10
+ PlanViewNode,
11
+ PlanViewResponse,
12
+ SerializableExecutionPlan,
13
+ SerializablePlanNode,
14
+ } from '@lota-sdk/shared'
15
+
16
+ import { agentRoster } from '../config/agent-defaults'
17
+ import { serverLogger } from '../config/logger'
18
+ import { executionPlanService } from './execution-plan.service'
19
+ import { workstreamService } from './workstream.service'
20
+ import type { NormalizedWorkstream } from './workstream.types'
21
+
22
+ const BOARD_COLUMN_ORDER = ['ready', 'running', 'awaiting-human', 'completed', 'blocked', 'failed'] as const
23
+ type BoardColumnStatus = (typeof BOARD_COLUMN_ORDER)[number]
24
+
25
+ const COLUMN_LABELS: Record<BoardColumnStatus, string> = {
26
+ ready: 'Ready',
27
+ running: 'Running',
28
+ 'awaiting-human': 'Awaiting Human',
29
+ completed: 'Completed',
30
+ blocked: 'Blocked',
31
+ failed: 'Failed',
32
+ }
33
+
34
+ type ActivePlanEntry = {
35
+ plan: SerializableExecutionPlan
36
+ workstream: Pick<NormalizedWorkstream, 'id' | 'title' | 'isRunning' | 'updatedAt'>
37
+ }
38
+
39
+ type AgentActivityDeps = {
40
+ executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForWorkstream'>
41
+ workstreamService: Pick<typeof workstreamService, 'listWorkstreams'>
42
+ }
43
+
44
+ function normalizeCardStatus(status: string): BoardColumnStatus {
45
+ if (status === 'pending' || status === 'scheduled') return 'ready'
46
+ if (status === 'partial') return 'completed'
47
+ if (status === 'running' || status === 'awaiting-human' || status === 'completed' || status === 'blocked') {
48
+ return status
49
+ }
50
+ if (status === 'failed') return 'failed'
51
+ return 'blocked'
52
+ }
53
+
54
+ function isCompletedStatus(status: string): boolean {
55
+ return status === 'completed' || status === 'partial' || status === 'skipped'
56
+ }
57
+
58
+ function createEmptyCounts(): AgentActivityCounts {
59
+ return { running: 0, ready: 0, pending: 0, awaitingHuman: 0, blocked: 0, completed: 0, failed: 0 }
60
+ }
61
+
62
+ function maxIsoDate(current: string | null, candidate: string): string {
63
+ if (!current) return candidate
64
+ return new Date(candidate).getTime() > new Date(current).getTime() ? candidate : current
65
+ }
66
+
67
+ function incrementCounts(counts: AgentActivityCounts, rawStatus: string): void {
68
+ if (rawStatus === 'running') {
69
+ counts.running += 1
70
+ return
71
+ }
72
+ if (rawStatus === 'ready') {
73
+ counts.ready += 1
74
+ return
75
+ }
76
+ if (rawStatus === 'pending' || rawStatus === 'scheduled') {
77
+ counts.pending += 1
78
+ return
79
+ }
80
+ if (rawStatus === 'awaiting-human') {
81
+ counts.awaitingHuman += 1
82
+ return
83
+ }
84
+ if (rawStatus === 'blocked') {
85
+ counts.blocked += 1
86
+ return
87
+ }
88
+ if (rawStatus === 'failed') {
89
+ counts.failed += 1
90
+ return
91
+ }
92
+ if (rawStatus === 'completed' || rawStatus === 'partial') {
93
+ counts.completed += 1
94
+ }
95
+ }
96
+
97
+ export function planNodeToCard(
98
+ node: SerializablePlanNode,
99
+ plan: SerializableExecutionPlan,
100
+ workstreamId: string,
101
+ workstreamTitle: string,
102
+ ): PlanNodeCard {
103
+ const approval = plan.approvals.find((candidate) => candidate.nodeId === node.id && candidate.status === 'pending')
104
+
105
+ return {
106
+ nodeId: node.id,
107
+ label: node.label,
108
+ objective: node.objective,
109
+ status: normalizeCardStatus(node.status),
110
+ ownerType: node.owner.executorType,
111
+ ownerRef: node.owner.ref,
112
+ planRunId: plan.runId,
113
+ planTitle: plan.title,
114
+ workstreamId,
115
+ workstreamTitle,
116
+ nodeType: node.type,
117
+ artifactCount: plan.artifacts.filter((artifact) => artifact.nodeId === node.id).length,
118
+ hasApproval: Boolean(approval),
119
+ approvalId: approval?.id ?? null,
120
+ approvalStatus: approval?.status ?? null,
121
+ blockedReason: node.blockedReason ?? null,
122
+ latestNotes: node.latestNotes ?? null,
123
+ startedAt: node.startedAt ?? null,
124
+ completedAt: node.completedAt ?? null,
125
+ readyAt: node.readyAt ?? null,
126
+ }
127
+ }
128
+
129
+ function buildPlanViewNode(
130
+ node: SerializablePlanNode,
131
+ plan: SerializableExecutionPlan,
132
+ workstreamId: string,
133
+ workstreamTitle: string,
134
+ ): PlanViewNode {
135
+ return {
136
+ ...planNodeToCard(node, plan, workstreamId, workstreamTitle),
137
+ deliverableNames: node.deliverables.map((deliverable) => deliverable.name),
138
+ upstreamNodeIds: node.upstreamNodeIds,
139
+ downstreamNodeIds: node.downstreamNodeIds,
140
+ }
141
+ }
142
+
143
+ export class AgentActivityService {
144
+ constructor(private readonly deps: AgentActivityDeps = { executionPlanService, workstreamService }) {}
145
+
146
+ async getBoard(userRef: string, orgRef: string): Promise<PlanBoardResponse> {
147
+ const activePlans = await this.getAllActivePlans(userRef, orgRef)
148
+ const cards = activePlans.flatMap(({ plan, workstream }) =>
149
+ plan.nodes.map((node) => planNodeToCard(node, plan, workstream.id, workstream.title)),
150
+ )
151
+
152
+ const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
153
+ status,
154
+ label: COLUMN_LABELS[status],
155
+ nodes: cards.filter((card) => card.status === status),
156
+ }))
157
+
158
+ return {
159
+ columns,
160
+ summary: {
161
+ totalNodes: cards.length,
162
+ completedNodes: cards.filter((card) => card.status === 'completed').length,
163
+ activePlanCount: activePlans.length,
164
+ pendingApprovalCount: cards.filter((card) => card.hasApproval).length,
165
+ },
166
+ }
167
+ }
168
+
169
+ async getPlanView(orgRef: string, planRunId: string, userRef: string): Promise<PlanViewResponse | null> {
170
+ const activePlans = await this.getAllActivePlans(userRef, orgRef)
171
+ const match = activePlans.find(({ plan }) => plan.runId === planRunId)
172
+ if (!match) return null
173
+
174
+ const { plan, workstream } = match
175
+ return {
176
+ planRunId: plan.runId,
177
+ title: plan.title,
178
+ objective: plan.objective,
179
+ status: plan.status,
180
+ leadAgentId: plan.leadAgentId,
181
+ progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
182
+ nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, workstream.id, workstream.title)),
183
+ edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
184
+ }
185
+ }
186
+
187
+ async getMyTasks(userRef: string, orgRef: string): Promise<MyTasksResponse> {
188
+ const activePlans = await this.getAllActivePlans(userRef, orgRef)
189
+ const tasks: PlanNodeCard[] = []
190
+
191
+ for (const { plan, workstream } of activePlans) {
192
+ for (const node of plan.nodes) {
193
+ const humanOwned = node.owner.executorType === 'user'
194
+ const awaitingHuman = node.status === 'awaiting-human'
195
+ if (!humanOwned && !awaitingHuman) continue
196
+
197
+ tasks.push(planNodeToCard(node, plan, workstream.id, workstream.title))
198
+ }
199
+ }
200
+
201
+ return { tasks, pendingApprovalCount: tasks.filter((task) => task.hasApproval).length }
202
+ }
203
+
204
+ async getAgentActivity(userRef: string, orgRef: string): Promise<AgentActivityResponse> {
205
+ const activePlans = await this.getAllActivePlans(userRef, orgRef)
206
+ const activityByAgent = new Map<string, AgentActivityEntry>()
207
+
208
+ for (const agentId of agentRoster) {
209
+ activityByAgent.set(agentId, this.createEmptyEntry(agentId))
210
+ }
211
+
212
+ for (const { plan, workstream } of activePlans) {
213
+ const involvedAgents = new Set<string>()
214
+
215
+ for (const node of plan.nodes) {
216
+ if (node.owner.executorType !== 'agent') continue
217
+
218
+ const agentId = node.owner.ref
219
+ const entry = this.ensureEntry(activityByAgent, agentId)
220
+ involvedAgents.add(agentId)
221
+ incrementCounts(entry.counts, node.status)
222
+
223
+ if (!isCompletedStatus(node.status)) {
224
+ entry.tasks.push(planNodeToCard(node, plan, workstream.id, workstream.title))
225
+ }
226
+ }
227
+
228
+ if (plan.leadAgentId.trim()) {
229
+ const leadEntry = this.ensureEntry(activityByAgent, plan.leadAgentId)
230
+ leadEntry.isLeadingActivePlan = true
231
+ involvedAgents.add(plan.leadAgentId)
232
+ }
233
+
234
+ for (const agentId of involvedAgents) {
235
+ const entry = this.ensureEntry(activityByAgent, agentId)
236
+ this.ensureProjectEntry(entry.projects, {
237
+ workstreamId: workstream.id,
238
+ workstreamTitle: workstream.title,
239
+ planRunId: plan.runId,
240
+ planTitle: plan.title,
241
+ status: plan.status,
242
+ })
243
+
244
+ entry.isRunning = entry.isRunning || workstream.isRunning
245
+ entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, workstream.updatedAt)
246
+ }
247
+ }
248
+
249
+ const userTasks = await this.getMyTasks(userRef, orgRef)
250
+ const agents = [...activityByAgent.values()].sort((left, right) => {
251
+ const leftIndex = agentRoster.indexOf(left.agentId)
252
+ const rightIndex = agentRoster.indexOf(right.agentId)
253
+ if (leftIndex !== -1 || rightIndex !== -1) {
254
+ if (leftIndex === -1) return 1
255
+ if (rightIndex === -1) return -1
256
+ return leftIndex - rightIndex
257
+ }
258
+ return left.agentId.localeCompare(right.agentId)
259
+ })
260
+
261
+ return {
262
+ agents,
263
+ userActivity: {
264
+ taskCount: userTasks.tasks.length,
265
+ pendingApprovalCount: userTasks.pendingApprovalCount,
266
+ awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
267
+ lastActiveAt: activePlans.reduce<string | null>(
268
+ (latest, entry) => maxIsoDate(latest, entry.workstream.updatedAt),
269
+ null,
270
+ ),
271
+ },
272
+ totalActivePlans: activePlans.length,
273
+ }
274
+ }
275
+
276
+ async getAllActivePlans(userRef: string, orgRef: string): Promise<ActivePlanEntry[]> {
277
+ const workstreams = await this.listRelevantWorkstreams(userRef, orgRef)
278
+ const planResults = await Promise.all(
279
+ workstreams.map(async (workstream) => {
280
+ try {
281
+ const plans = await this.deps.executionPlanService.getActivePlansForWorkstream(workstream.id)
282
+ return plans.map((plan) => ({ plan, workstream }))
283
+ } catch (error) {
284
+ serverLogger.error`Failed to load active plans for workstream ${workstream.id}: ${error}`
285
+ return []
286
+ }
287
+ }),
288
+ )
289
+
290
+ return planResults.flat()
291
+ }
292
+
293
+ private async listRelevantWorkstreams(userRef: string, orgRef: string): Promise<NormalizedWorkstream[]> {
294
+ const [direct, core, group] = await Promise.all([
295
+ this.deps.workstreamService.listWorkstreams(userRef, orgRef, { mode: 'direct', includeArchived: false }),
296
+ this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
297
+ mode: 'group',
298
+ core: true,
299
+ includeArchived: false,
300
+ }),
301
+ this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
302
+ mode: 'group',
303
+ core: false,
304
+ includeArchived: false,
305
+ take: 500,
306
+ page: 1,
307
+ }),
308
+ ])
309
+
310
+ const deduped = new Map<string, NormalizedWorkstream>()
311
+ for (const workstream of [...direct.workstreams, ...core.workstreams, ...group.workstreams]) {
312
+ deduped.set(workstream.id, workstream)
313
+ }
314
+
315
+ return [...deduped.values()]
316
+ }
317
+
318
+ private createEmptyEntry(agentId: string): AgentActivityEntry {
319
+ return {
320
+ agentId,
321
+ counts: createEmptyCounts(),
322
+ tasks: [],
323
+ projects: [],
324
+ isLeadingActivePlan: false,
325
+ isRunning: false,
326
+ lastActiveAt: null,
327
+ }
328
+ }
329
+
330
+ private ensureEntry(entries: Map<string, AgentActivityEntry>, agentId: string): AgentActivityEntry {
331
+ const existing = entries.get(agentId)
332
+ if (existing) {
333
+ return existing
334
+ }
335
+
336
+ const created = this.createEmptyEntry(agentId)
337
+ entries.set(agentId, created)
338
+ return created
339
+ }
340
+
341
+ private ensureProjectEntry(projects: AgentProjectEntry[], next: AgentProjectEntry): void {
342
+ if (projects.some((project) => project.planRunId === next.planRunId)) {
343
+ return
344
+ }
345
+
346
+ projects.push(next)
347
+ }
348
+ }
349
+
350
+ export const agentActivityService = new AgentActivityService()
@@ -1,5 +1,6 @@
1
1
  export * from './adaptive-playbook.service'
2
2
  export * from './agent-executor.service'
3
+ export * from './agent-activity.service'
3
4
  export * from './artifact-provenance.service'
4
5
  export * from './attachment.service'
5
6
  export * from './autonomous-job.service'
@@ -7,6 +7,7 @@ import {
7
7
  SubmitExecutionNodeResultArgsSchema,
8
8
  getLatestExecutionPlanResult,
9
9
  } from '@lota-sdk/shared'
10
+ import type { PlanDraft } from '@lota-sdk/shared'
10
11
  import { tool } from 'ai'
11
12
 
12
13
  import type { RecordIdRef } from '../db/record-id'
@@ -17,17 +18,20 @@ export function createCreateExecutionPlanTool(params: {
17
18
  workstreamId: RecordIdRef
18
19
  agentId: string
19
20
  onPlanChanged?: () => void
21
+ validateInlinePlan?: (draft: PlanDraft) => void
20
22
  }) {
21
23
  return tool({
22
24
  description:
23
- 'Create a contract-driven execution plan for this workstream. Nodes must define deliverables, success criteria, completion checks, and executor policy.',
25
+ 'Create a contract-driven execution plan for this workstream. Use createProjectWithPlan instead when the work needs a dedicated project workstream or a larger 3+ node plan.',
24
26
  inputSchema: CreateExecutionPlanArgsSchema,
25
27
  execute: async (input) => {
28
+ const { targetWorkstreamId, ...draft } = input
29
+ params.validateInlinePlan?.(draft)
26
30
  const result = await executionPlanService.createPlan({
27
31
  organizationId: params.orgId,
28
- workstreamId: params.workstreamId,
32
+ workstreamId: targetWorkstreamId ?? params.workstreamId,
29
33
  leadAgentId: params.agentId,
30
- input,
34
+ input: draft,
31
35
  })
32
36
  params.onPlanChanged?.()
33
37
  return result
@@ -1,6 +1,7 @@
1
1
  export * from './execution-plan.tool'
2
2
  export * from './fetch-webpage.tool'
3
3
  export * from './memory-block.tool'
4
+ export * from './project-with-plan.tool'
4
5
  export * from './read-file-parts.tool'
5
6
  export * from './remember-memory.tool'
6
7
  export * from './research-topic.tool'
@@ -0,0 +1,87 @@
1
+ import type { CreateProjectWithPlanResultData } from '@lota-sdk/shared'
2
+ import { CreateProjectWithPlanArgsSchema, expandAgentPlanDraft } from '@lota-sdk/shared'
3
+ import { tool } from 'ai'
4
+
5
+ import type { RecordIdRef } from '../db/record-id'
6
+ import { recordIdToString } from '../db/record-id'
7
+ import { TABLES } from '../db/tables'
8
+ import { executionPlanService } from '../services/execution-plan.service'
9
+ import { workstreamService } from '../services/workstream.service'
10
+
11
+ type ProjectWithPlanWorkstreamService = Pick<
12
+ typeof workstreamService,
13
+ 'createWorkstream' | 'deleteWorkstream' | 'getWorkstream'
14
+ >
15
+
16
+ type ProjectWithPlanExecutionPlanService = Pick<
17
+ typeof executionPlanService,
18
+ 'createPlan' | 'getActivePlansForWorkstream'
19
+ >
20
+
21
+ export function createCreateProjectWithPlanTool(params: {
22
+ orgId: RecordIdRef
23
+ userId: RecordIdRef
24
+ agentId: string
25
+ workstreamService?: ProjectWithPlanWorkstreamService
26
+ executionPlanService?: ProjectWithPlanExecutionPlanService
27
+ onPlanChanged?: () => void
28
+ }) {
29
+ const resolvedWorkstreamService = params.workstreamService ?? workstreamService
30
+ const resolvedExecutionPlanService = params.executionPlanService ?? executionPlanService
31
+
32
+ return tool({
33
+ description:
34
+ 'Create a dedicated project workstream with an execution plan, or add a simplified agent-authored plan to an existing target workstream.',
35
+ inputSchema: CreateProjectWithPlanArgsSchema,
36
+ execute: async (input): Promise<CreateProjectWithPlanResultData> => {
37
+ const { projectTitle, targetWorkstreamId, ...draftInput } = input
38
+ const expandedDraft = expandAgentPlanDraft(draftInput)
39
+
40
+ const targetWorkstream = targetWorkstreamId
41
+ ? await resolvedWorkstreamService.getWorkstream(targetWorkstreamId)
42
+ : await resolvedWorkstreamService.createWorkstream(params.userId, params.orgId, {
43
+ title: projectTitle,
44
+ mode: 'group',
45
+ })
46
+
47
+ if (targetWorkstream.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
48
+ throw new Error('Target workstream belongs to a different organization.')
49
+ }
50
+ if (targetWorkstream.userId !== recordIdToString(params.userId, TABLES.USER)) {
51
+ throw new Error('Target workstream belongs to a different user.')
52
+ }
53
+
54
+ const existingPlans = await resolvedExecutionPlanService.getActivePlansForWorkstream(targetWorkstream.id)
55
+ if (!targetWorkstream.core && existingPlans.length > 0) {
56
+ throw new Error(
57
+ 'This workstream already has an active execution plan. Use replaceExecutionPlan or target a core workstream.',
58
+ )
59
+ }
60
+
61
+ const createdWorkstream = !targetWorkstreamId
62
+
63
+ try {
64
+ const result = await resolvedExecutionPlanService.createPlan({
65
+ organizationId: params.orgId,
66
+ workstreamId: targetWorkstream.id,
67
+ leadAgentId: params.agentId,
68
+ input: expandedDraft,
69
+ })
70
+
71
+ params.onPlanChanged?.()
72
+
73
+ return {
74
+ ...result,
75
+ workstreamId: targetWorkstream.id,
76
+ workstreamTitle: targetWorkstream.title,
77
+ createdWorkstream,
78
+ }
79
+ } catch (error) {
80
+ if (createdWorkstream) {
81
+ await resolvedWorkstreamService.deleteWorkstream(targetWorkstream.id).catch(() => {})
82
+ }
83
+ throw error
84
+ }
85
+ },
86
+ })
87
+ }