@lota-sdk/core 0.1.14 → 0.1.16
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 +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +12 -5
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +386 -58
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +87 -20
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +5 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { getRedisConnection } from '../redis'
|
|
4
|
+
|
|
5
|
+
const PlanWorkspaceEntrySchema = z.object({
|
|
6
|
+
value: z.unknown(),
|
|
7
|
+
version: z.number(),
|
|
8
|
+
writeSequence: z.number(),
|
|
9
|
+
nodeId: z.string(),
|
|
10
|
+
timestamp: z.number(),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export type PlanWorkspaceEntry = z.infer<typeof PlanWorkspaceEntrySchema>
|
|
14
|
+
|
|
15
|
+
class PlanWorkspaceService {
|
|
16
|
+
private keyPrefix = 'plan-workspace:'
|
|
17
|
+
|
|
18
|
+
async write(params: {
|
|
19
|
+
runId: string
|
|
20
|
+
nodeId: string
|
|
21
|
+
key: string
|
|
22
|
+
value: unknown
|
|
23
|
+
version: number
|
|
24
|
+
checkpointSequence: number
|
|
25
|
+
}): Promise<void> {
|
|
26
|
+
const redis = getRedisConnection()
|
|
27
|
+
const hashKey = `${this.keyPrefix}${params.runId}`
|
|
28
|
+
const fieldKey = `${params.nodeId}:${params.key}`
|
|
29
|
+
const entry: PlanWorkspaceEntry = {
|
|
30
|
+
value: params.value,
|
|
31
|
+
version: params.version,
|
|
32
|
+
writeSequence: params.checkpointSequence,
|
|
33
|
+
nodeId: params.nodeId,
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
}
|
|
36
|
+
await redis.hset(hashKey, fieldKey, JSON.stringify(entry))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async snapshotRead(params: {
|
|
40
|
+
runId: string
|
|
41
|
+
readerNodeId: string
|
|
42
|
+
snapshotSequence?: number
|
|
43
|
+
}): Promise<Record<string, PlanWorkspaceEntry>> {
|
|
44
|
+
const redis = getRedisConnection()
|
|
45
|
+
const hashKey = `${this.keyPrefix}${params.runId}`
|
|
46
|
+
const nodePrefix = `${params.readerNodeId}:`
|
|
47
|
+
const all = await redis.hgetall(hashKey)
|
|
48
|
+
const result: Record<string, PlanWorkspaceEntry> = {}
|
|
49
|
+
for (const [fieldKey, raw] of Object.entries(all)) {
|
|
50
|
+
if (!fieldKey.startsWith(nodePrefix)) continue
|
|
51
|
+
const entry = PlanWorkspaceEntrySchema.parse(JSON.parse(raw))
|
|
52
|
+
if (params.snapshotSequence !== undefined && entry.writeSequence > params.snapshotSequence) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
result[fieldKey] = entry
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async currentRead(params: { runId: string; key?: string }): Promise<Record<string, PlanWorkspaceEntry>> {
|
|
61
|
+
const redis = getRedisConnection()
|
|
62
|
+
const hashKey = `${this.keyPrefix}${params.runId}`
|
|
63
|
+
if (params.key) {
|
|
64
|
+
// Support both raw field keys (e.g. 'nodeId:key') and plain keys
|
|
65
|
+
const raw = await redis.hget(hashKey, params.key)
|
|
66
|
+
if (!raw) return {}
|
|
67
|
+
return { [params.key]: PlanWorkspaceEntrySchema.parse(JSON.parse(raw)) }
|
|
68
|
+
}
|
|
69
|
+
const all = await redis.hgetall(hashKey)
|
|
70
|
+
const result: Record<string, PlanWorkspaceEntry> = {}
|
|
71
|
+
for (const [k, v] of Object.entries(all)) {
|
|
72
|
+
result[k] = PlanWorkspaceEntrySchema.parse(JSON.parse(v))
|
|
73
|
+
}
|
|
74
|
+
return result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async cleanup(runId: string): Promise<void> {
|
|
78
|
+
const redis = getRedisConnection()
|
|
79
|
+
await redis.del(`${this.keyPrefix}${runId}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const planWorkspaceService = new PlanWorkspaceService()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { PlanTemplateRecord } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import type { RecordIdInput } from '../db/record-id'
|
|
4
|
+
import type { PlaybookContribution } from '../runtime/plugin-types'
|
|
5
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
6
|
+
import { planTemplateService } from './plan-template.service'
|
|
7
|
+
|
|
8
|
+
class PlaybookRegistryService {
|
|
9
|
+
collectPlaybooks(): PlaybookContribution[] {
|
|
10
|
+
const plugins = getRuntimeConfig().pluginRuntime ?? {}
|
|
11
|
+
const playbooks: PlaybookContribution[] = []
|
|
12
|
+
for (const plugin of Object.values(plugins)) {
|
|
13
|
+
if (plugin.playbookContributor?.playbooks) {
|
|
14
|
+
playbooks.push(...plugin.playbookContributor.playbooks)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return playbooks
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async syncPlaybookTemplates(organizationId: RecordIdInput): Promise<PlanTemplateRecord[]> {
|
|
21
|
+
const playbooks = this.collectPlaybooks()
|
|
22
|
+
const templates: PlanTemplateRecord[] = []
|
|
23
|
+
const existing = await planTemplateService.listTemplates(organizationId, { source: 'playbook' })
|
|
24
|
+
|
|
25
|
+
for (const pb of playbooks) {
|
|
26
|
+
const match = existing.find((t) => t.sourceRef === pb.name)
|
|
27
|
+
if (match) {
|
|
28
|
+
const updated = await planTemplateService.updateTemplate(match.id, { draft: pb.draft, tags: pb.tags })
|
|
29
|
+
templates.push(updated)
|
|
30
|
+
} else {
|
|
31
|
+
const created = await planTemplateService.createTemplate({
|
|
32
|
+
organizationId,
|
|
33
|
+
name: pb.name,
|
|
34
|
+
description: pb.description,
|
|
35
|
+
draft: pb.draft,
|
|
36
|
+
tags: pb.tags,
|
|
37
|
+
source: 'playbook',
|
|
38
|
+
sourceRef: pb.name,
|
|
39
|
+
})
|
|
40
|
+
templates.push(created)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return templates
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async instantiatePlaybook(params: {
|
|
48
|
+
name: string
|
|
49
|
+
organizationId: RecordIdInput
|
|
50
|
+
workstreamId: RecordIdInput
|
|
51
|
+
leadAgentId: string
|
|
52
|
+
}): Promise<unknown> {
|
|
53
|
+
const templates = await planTemplateService.listTemplates(params.organizationId, { source: 'playbook' })
|
|
54
|
+
const template = templates.find((t) => t.sourceRef === params.name)
|
|
55
|
+
if (!template) {
|
|
56
|
+
throw new Error(`Playbook "${params.name}" not found.`)
|
|
57
|
+
}
|
|
58
|
+
return planTemplateService.instantiate({
|
|
59
|
+
templateId: template.id,
|
|
60
|
+
organizationId: params.organizationId,
|
|
61
|
+
workstreamId: params.workstreamId,
|
|
62
|
+
leadAgentId: params.leadAgentId,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const playbookRegistryService = new PlaybookRegistryService()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, PluginPlanNodeOwner } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import type { LotaPlugin, PluginNodeExecutionParams } from '../runtime/plugin-types'
|
|
4
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
5
|
+
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
6
|
+
|
|
7
|
+
function getPluginRuntime() {
|
|
8
|
+
return (getRuntimeConfig().pluginRuntime ?? {}) as Record<string, LotaPlugin | undefined>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildPluginExecutionParams(params: {
|
|
12
|
+
owner: PluginPlanNodeOwner
|
|
13
|
+
nodeSpec: PlanNodeSpec
|
|
14
|
+
resolvedInput: Record<string, unknown>
|
|
15
|
+
context: OwnershipDispatchContext
|
|
16
|
+
}): PluginNodeExecutionParams {
|
|
17
|
+
return {
|
|
18
|
+
operation: params.owner.operation,
|
|
19
|
+
nodeSpec: params.nodeSpec,
|
|
20
|
+
inputs: params.resolvedInput,
|
|
21
|
+
context: {
|
|
22
|
+
organizationId: params.context.organizationId,
|
|
23
|
+
workstreamId: params.context.workstreamId,
|
|
24
|
+
planId: params.context.planId,
|
|
25
|
+
nodeId: params.context.nodeId,
|
|
26
|
+
...(params.context.userId ? { userId: params.context.userId } : {}),
|
|
27
|
+
...(params.context.userName ? { userName: params.context.userName } : {}),
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class PluginExecutorService {
|
|
33
|
+
validateOwner(owner: PluginPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
|
|
34
|
+
const plugin = getPluginRuntime()[owner.ref]
|
|
35
|
+
if (!plugin) {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
severity: 'blocking',
|
|
39
|
+
code: 'plugin_executor_missing',
|
|
40
|
+
message: `Node "${nodeId}" references unknown plugin executor "${owner.ref}".`,
|
|
41
|
+
nodeId,
|
|
42
|
+
detail: { pluginRef: owner.ref },
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nodeExecutor = plugin.nodeExecutor
|
|
48
|
+
if (!nodeExecutor) {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
severity: 'blocking',
|
|
52
|
+
code: 'plugin_node_executor_missing',
|
|
53
|
+
message: `Plugin "${owner.ref}" does not expose a node executor.`,
|
|
54
|
+
nodeId,
|
|
55
|
+
detail: { pluginRef: owner.ref },
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!nodeExecutor.supportedOperations.includes(owner.operation)) {
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
severity: 'blocking',
|
|
64
|
+
code: 'plugin_operation_missing',
|
|
65
|
+
message: `Plugin "${owner.ref}" does not support operation "${owner.operation}".`,
|
|
66
|
+
nodeId,
|
|
67
|
+
detail: { pluginRef: owner.ref, operation: owner.operation },
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async executeNode(params: {
|
|
76
|
+
nodeSpec: PlanNodeSpec
|
|
77
|
+
resolvedInput: Record<string, unknown>
|
|
78
|
+
context: OwnershipDispatchContext
|
|
79
|
+
}): Promise<PlanNodeResult> {
|
|
80
|
+
if (params.nodeSpec.owner.executorType !== 'plugin') {
|
|
81
|
+
throw new Error(`PluginExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const plugin = getPluginRuntime()[params.nodeSpec.owner.ref]
|
|
85
|
+
const nodeExecutor = plugin?.nodeExecutor
|
|
86
|
+
if (!plugin || !nodeExecutor || !nodeExecutor.supportedOperations.includes(params.nodeSpec.owner.operation)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Plugin executor ${params.nodeSpec.owner.ref}.${params.nodeSpec.owner.operation} is not registered.`,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return nodeExecutor.executeNode(
|
|
93
|
+
buildPluginExecutionParams({
|
|
94
|
+
owner: params.nodeSpec.owner,
|
|
95
|
+
nodeSpec: params.nodeSpec,
|
|
96
|
+
resolvedInput: params.resolvedInput,
|
|
97
|
+
context: params.context,
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const pluginExecutorService = new PluginExecutorService()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { NodeQualityMetricsSchema } from '@lota-sdk/shared'
|
|
2
|
+
import type { NodeQualityMetrics } from '@lota-sdk/shared'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import { ensureRecordId } from '../db/record-id'
|
|
6
|
+
import { databaseService } from '../db/service'
|
|
7
|
+
import { TABLES } from '../db/tables'
|
|
8
|
+
|
|
9
|
+
const QualityMetricRowSchema = NodeQualityMetricsSchema.extend({
|
|
10
|
+
id: z.unknown(),
|
|
11
|
+
organizationId: z.unknown(),
|
|
12
|
+
runId: z.unknown(),
|
|
13
|
+
nodeId: z.string().optional(),
|
|
14
|
+
createdAt: z.coerce.date().optional(),
|
|
15
|
+
updatedAt: z.coerce.date().optional(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
class QualityMetricsService {
|
|
19
|
+
async recordNodeMetrics(params: {
|
|
20
|
+
organizationId: string
|
|
21
|
+
runId: string
|
|
22
|
+
nodeId: string
|
|
23
|
+
metrics: NodeQualityMetrics
|
|
24
|
+
}): Promise<void> {
|
|
25
|
+
await databaseService.create(
|
|
26
|
+
TABLES.QUALITY_METRIC,
|
|
27
|
+
{
|
|
28
|
+
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
29
|
+
runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
|
|
30
|
+
nodeId: params.nodeId,
|
|
31
|
+
ownerRef: params.metrics.ownerRef,
|
|
32
|
+
ownerType: params.metrics.ownerType,
|
|
33
|
+
nodeType: params.metrics.nodeType,
|
|
34
|
+
executionTimeMs: params.metrics.executionTimeMs,
|
|
35
|
+
attemptCount: params.metrics.attemptCount,
|
|
36
|
+
artifactCount: params.metrics.artifactCount,
|
|
37
|
+
validationIssueCount: params.metrics.validationIssueCount,
|
|
38
|
+
},
|
|
39
|
+
QualityMetricRowSchema,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async recordCycleMetrics(params: { organizationId: string; runId: string }): Promise<void> {
|
|
44
|
+
const existing = await databaseService.findMany(
|
|
45
|
+
TABLES.QUALITY_METRIC,
|
|
46
|
+
{ runId: ensureRecordId(params.runId, TABLES.PLAN_RUN) },
|
|
47
|
+
QualityMetricRowSchema,
|
|
48
|
+
{ orderBy: 'createdAt', orderDir: 'ASC' },
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if (!Array.isArray(existing) || existing.length === 0) return
|
|
52
|
+
|
|
53
|
+
const totalExecutionTime = existing.reduce(
|
|
54
|
+
(sum, m) => sum + (typeof m.executionTimeMs === 'number' ? m.executionTimeMs : 0),
|
|
55
|
+
0,
|
|
56
|
+
)
|
|
57
|
+
const totalAttempts = existing.reduce(
|
|
58
|
+
(sum, m) => sum + (typeof m.attemptCount === 'number' ? m.attemptCount : 0),
|
|
59
|
+
0,
|
|
60
|
+
)
|
|
61
|
+
const totalIssues = existing.reduce(
|
|
62
|
+
(sum, m) => sum + (typeof m.validationIssueCount === 'number' ? m.validationIssueCount : 0),
|
|
63
|
+
0,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
await databaseService.create(
|
|
67
|
+
TABLES.QUALITY_METRIC,
|
|
68
|
+
{
|
|
69
|
+
organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
|
|
70
|
+
runId: ensureRecordId(params.runId, TABLES.PLAN_RUN),
|
|
71
|
+
ownerRef: 'cycle-summary',
|
|
72
|
+
ownerType: 'system',
|
|
73
|
+
nodeType: 'action',
|
|
74
|
+
executionTimeMs: totalExecutionTime,
|
|
75
|
+
attemptCount: totalAttempts,
|
|
76
|
+
artifactCount: existing.length,
|
|
77
|
+
validationIssueCount: totalIssues,
|
|
78
|
+
},
|
|
79
|
+
QualityMetricRowSchema,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
detectRegression(params: {
|
|
84
|
+
metrics: Array<{ executionTimeMs: number; validationIssueCount: number }>
|
|
85
|
+
window?: number
|
|
86
|
+
}): { detected: boolean; trend?: 'improving' | 'stable' | 'regressing' } {
|
|
87
|
+
const window = params.window ?? 5
|
|
88
|
+
const recent = params.metrics.slice(-window)
|
|
89
|
+
if (recent.length < 2) return { detected: false, trend: 'stable' }
|
|
90
|
+
|
|
91
|
+
const mid = Math.floor(recent.length / 2)
|
|
92
|
+
const firstHalf = recent.slice(0, mid)
|
|
93
|
+
const secondHalf = recent.slice(mid)
|
|
94
|
+
|
|
95
|
+
const avgFirst = firstHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / firstHalf.length
|
|
96
|
+
const avgSecond = secondHalf.reduce((sum, m) => sum + m.validationIssueCount, 0) / secondHalf.length
|
|
97
|
+
|
|
98
|
+
const timeFirst = firstHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / firstHalf.length
|
|
99
|
+
const timeSecond = secondHalf.reduce((sum, m) => sum + m.executionTimeMs, 0) / secondHalf.length
|
|
100
|
+
|
|
101
|
+
const issueRegression = avgFirst > 0 && avgSecond > avgFirst * 1.5
|
|
102
|
+
const timeRegression = timeFirst > 0 && timeSecond > timeFirst * 2
|
|
103
|
+
|
|
104
|
+
if (issueRegression || timeRegression) {
|
|
105
|
+
return { detected: true, trend: 'regressing' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const issueImproving = avgFirst > 0 && avgSecond < avgFirst * 0.7
|
|
109
|
+
const timeImproving = timeFirst > 0 && timeSecond < timeFirst * 0.7
|
|
110
|
+
|
|
111
|
+
if (issueImproving || timeImproving) {
|
|
112
|
+
return { detected: false, trend: 'improving' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { detected: false, trend: 'stable' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getMetricsByOwner(params: {
|
|
119
|
+
organizationId: string
|
|
120
|
+
ownerRef: string
|
|
121
|
+
limit?: number
|
|
122
|
+
}): Promise<z.infer<typeof QualityMetricRowSchema>[]> {
|
|
123
|
+
return databaseService.findMany(
|
|
124
|
+
TABLES.QUALITY_METRIC,
|
|
125
|
+
{ organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION), ownerRef: params.ownerRef },
|
|
126
|
+
QualityMetricRowSchema,
|
|
127
|
+
{ orderBy: 'createdAt', orderDir: 'DESC', limit: params.limit ?? 20 },
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const qualityMetricsService = new QualityMetricsService()
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { createHelperModelRuntime } from '../runtime/helper-model'
|
|
2
|
+
import { normalizeTitle } from '../runtime/title-helpers'
|
|
2
3
|
import {
|
|
3
4
|
createRecentActivityTitleRefinerAgent,
|
|
4
|
-
|
|
5
|
+
RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
|
|
5
6
|
} from '../system-agents/recent-activity-title-refiner.agent'
|
|
6
|
-
import { compactWhitespace } from '../utils/string'
|
|
7
7
|
import { recentActivityService } from './recent-activity.service'
|
|
8
8
|
|
|
9
9
|
const RECENT_ACTIVITY_TITLE_TIMEOUT_MS = 60_000
|
|
10
10
|
|
|
11
|
-
function normalizeTitle(value: string): string {
|
|
12
|
-
const normalized = compactWhitespace(value)
|
|
13
|
-
.replace(/^["'`]+|["'`]+$/g, '')
|
|
14
|
-
.replace(/[.!?,;:]+$/g, '')
|
|
15
|
-
return normalized.length <= 80 ? normalized : normalized.slice(0, 80).trim()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
11
|
function buildRefinementPromptInput(
|
|
19
12
|
candidate: Awaited<ReturnType<typeof recentActivityService.getRefinementCandidate>>,
|
|
20
13
|
) {
|
|
@@ -52,7 +45,7 @@ class RecentActivityTitleService {
|
|
|
52
45
|
await this.helperRuntime.generateHelperText({
|
|
53
46
|
tag: 'recent-activity-title-refinement',
|
|
54
47
|
createAgent: createRecentActivityTitleRefinerAgent,
|
|
55
|
-
defaultSystemPrompt:
|
|
48
|
+
defaultSystemPrompt: RECENT_ACTIVITY_TITLE_REFINER_PROMPT,
|
|
56
49
|
timeoutMs: RECENT_ACTIVITY_TITLE_TIMEOUT_MS,
|
|
57
50
|
messages: [{ role: 'user', content: promptInput }],
|
|
58
51
|
}),
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
RecentActivitySchema,
|
|
9
9
|
RecentActivitySourceSchema,
|
|
10
10
|
RecentActivityTitleSourceSchema,
|
|
11
|
+
recordIdSchema,
|
|
11
12
|
} from '@lota-sdk/shared'
|
|
12
13
|
import type {
|
|
13
14
|
RecentActivity,
|
|
@@ -23,12 +24,12 @@ import type { RecordIdInput, RecordIdRef } from '../db/record-id'
|
|
|
23
24
|
import { databaseService } from '../db/service'
|
|
24
25
|
import { TABLES } from '../db/tables'
|
|
25
26
|
import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
|
|
26
|
-
import { compactWhitespace } from '../utils/string'
|
|
27
|
+
import { compactRecord, compactWhitespace, truncateText } from '../utils/string'
|
|
27
28
|
|
|
28
29
|
const RecentActivityEventRowSchema = z.object({
|
|
29
|
-
id:
|
|
30
|
-
organizationId:
|
|
31
|
-
userId:
|
|
30
|
+
id: recordIdSchema,
|
|
31
|
+
organizationId: recordIdSchema,
|
|
32
|
+
userId: recordIdSchema,
|
|
32
33
|
sourceEventId: z.string(),
|
|
33
34
|
source: RecentActivitySourceSchema,
|
|
34
35
|
kind: RecentActivityEventInputSchema.shape.kind,
|
|
@@ -39,14 +40,14 @@ const RecentActivityEventRowSchema = z.object({
|
|
|
39
40
|
sourceLabel: z.string(),
|
|
40
41
|
deepLink: RecentActivityDeepLinkSchema,
|
|
41
42
|
metadata: RecentActivityMetadataSchema.optional(),
|
|
42
|
-
occurredAt: z.
|
|
43
|
-
createdAt: z.
|
|
43
|
+
occurredAt: z.coerce.date(),
|
|
44
|
+
createdAt: z.coerce.date(),
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
const RecentActivityRowSchema = z.object({
|
|
47
|
-
id:
|
|
48
|
-
organizationId:
|
|
49
|
-
userId:
|
|
48
|
+
id: recordIdSchema,
|
|
49
|
+
organizationId: recordIdSchema,
|
|
50
|
+
userId: recordIdSchema,
|
|
50
51
|
mergeKey: z.string(),
|
|
51
52
|
kind: RecentActivityEventInputSchema.shape.kind,
|
|
52
53
|
targetKind: RecentActivityEventInputSchema.shape.targetKind,
|
|
@@ -57,27 +58,17 @@ const RecentActivityRowSchema = z.object({
|
|
|
57
58
|
sourceLabel: z.string(),
|
|
58
59
|
deepLink: RecentActivityDeepLinkSchema,
|
|
59
60
|
metadata: RecentActivityMetadataSchema.optional(),
|
|
60
|
-
latestEventId:
|
|
61
|
+
latestEventId: recordIdSchema.optional(),
|
|
61
62
|
latestSourceEventId: z.string().optional(),
|
|
62
|
-
latestEventAt: z.
|
|
63
|
-
titleRefinedAt: z.
|
|
64
|
-
createdAt: z.
|
|
65
|
-
updatedAt: z.
|
|
63
|
+
latestEventAt: z.coerce.date(),
|
|
64
|
+
titleRefinedAt: z.coerce.date().optional(),
|
|
65
|
+
createdAt: z.coerce.date(),
|
|
66
|
+
updatedAt: z.coerce.date(),
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
type RecentActivityEventRow = z.infer<typeof RecentActivityEventRowSchema>
|
|
69
70
|
type RecentActivityRow = z.infer<typeof RecentActivityRowSchema>
|
|
70
71
|
|
|
71
|
-
function compactRecord(value: Record<string, unknown>): Record<string, unknown> {
|
|
72
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== null && entry !== undefined))
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function clampText(value: string, maxLength: number): string {
|
|
76
|
-
const normalized = compactWhitespace(value)
|
|
77
|
-
if (normalized.length <= maxLength) return normalized
|
|
78
|
-
return normalized.slice(0, maxLength).trim()
|
|
79
|
-
}
|
|
80
|
-
|
|
81
72
|
function buildDeterministicRecordId(table: string, key: string): RecordId {
|
|
82
73
|
const digest = createHash('sha256').update(key).digest('hex')
|
|
83
74
|
return new RecordId(table, digest)
|
|
@@ -90,6 +81,11 @@ function shouldKeepExistingAgentTitle(existing: RecentActivityRow | null): boole
|
|
|
90
81
|
function buildRecentActivityAreaKey(
|
|
91
82
|
row: Pick<RecentActivityRow, 'targetKind' | 'targetId' | 'kind' | 'mergeKey' | 'metadata'>,
|
|
92
83
|
): string {
|
|
84
|
+
const workstreamId = row.metadata?.workstreamId
|
|
85
|
+
if (workstreamId) {
|
|
86
|
+
return `workstream:${compactWhitespace(workstreamId)}`
|
|
87
|
+
}
|
|
88
|
+
|
|
93
89
|
if (row.targetId) {
|
|
94
90
|
return `${row.targetKind}:${row.targetId}`
|
|
95
91
|
}
|
|
@@ -149,11 +145,11 @@ class RecentActivityService {
|
|
|
149
145
|
private sanitizeEvent(input: RecentActivityEventInput): RecentActivityEventInput {
|
|
150
146
|
return {
|
|
151
147
|
...input,
|
|
152
|
-
sourceEventId:
|
|
153
|
-
...(input.targetId ? { targetId:
|
|
154
|
-
mergeKey:
|
|
155
|
-
title:
|
|
156
|
-
sourceLabel:
|
|
148
|
+
sourceEventId: truncateText(input.sourceEventId, 200),
|
|
149
|
+
...(input.targetId ? { targetId: truncateText(input.targetId, 200) } : {}),
|
|
150
|
+
mergeKey: truncateText(input.mergeKey, 200),
|
|
151
|
+
title: truncateText(input.title, 140),
|
|
152
|
+
sourceLabel: truncateText(input.sourceLabel, 40),
|
|
157
153
|
...(input.metadata ? { metadata: RecentActivityMetadataSchema.parse(input.metadata) } : {}),
|
|
158
154
|
}
|
|
159
155
|
}
|
|
@@ -166,18 +162,12 @@ class RecentActivityService {
|
|
|
166
162
|
}): Promise<RecentActivity[]> {
|
|
167
163
|
await databaseService.connect()
|
|
168
164
|
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
event: candidate,
|
|
176
|
-
})
|
|
177
|
-
items.push(recorded.item)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return items
|
|
165
|
+
const results = await Promise.all(
|
|
166
|
+
params.events.map((candidate) =>
|
|
167
|
+
this.recordEvent({ orgId: params.orgId, userId: params.userId, source: params.source, event: candidate }),
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
return results.map((r) => r.item)
|
|
181
171
|
}
|
|
182
172
|
|
|
183
173
|
async recordEvent(params: {
|
|
@@ -315,7 +305,7 @@ class RecentActivityService {
|
|
|
315
305
|
const existing = await databaseService.findOne(TABLES.RECENT_ACTIVITY, { id: activityRef }, RecentActivityRowSchema)
|
|
316
306
|
if (!existing) return null
|
|
317
307
|
|
|
318
|
-
const nextTitle =
|
|
308
|
+
const nextTitle = truncateText(params.title, 80)
|
|
319
309
|
if (!nextTitle) return this.toPublicItem(existing)
|
|
320
310
|
if (compactWhitespace(nextTitle).toLowerCase() === compactWhitespace(existing.title).toLowerCase()) {
|
|
321
311
|
return this.toPublicItem(existing)
|
|
@@ -353,7 +343,7 @@ class RecentActivityService {
|
|
|
353
343
|
systemTitle: row.systemTitle,
|
|
354
344
|
sourceLabel: row.sourceLabel,
|
|
355
345
|
kind: row.kind,
|
|
356
|
-
metadata: RecentActivityMetadataSchema.parse(row.metadata
|
|
346
|
+
metadata: RecentActivityMetadataSchema.parse(row.metadata),
|
|
357
347
|
}
|
|
358
348
|
}
|
|
359
349
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class SkillResolverService {
|
|
2
|
+
async resolve(params: {
|
|
3
|
+
skillRef: string
|
|
4
|
+
organizationId: string
|
|
5
|
+
}): Promise<{ executorType: 'agent' | 'plugin'; ref: string; operation?: string } | null> {
|
|
6
|
+
// Dynamic import to avoid circular dependencies and enable testability
|
|
7
|
+
const { learnedSkillService } = await import('./learned-skill.service')
|
|
8
|
+
const skill = await learnedSkillService.findByNameOrTag(params.organizationId, params.skillRef)
|
|
9
|
+
if (!skill) return null
|
|
10
|
+
|
|
11
|
+
if (skill.agentId) {
|
|
12
|
+
return { executorType: 'agent', ref: skill.agentId }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const skillResolverService = new SkillResolverService()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { OwnershipDispatchContext, PlanNodeResult, PlanNodeSpec, SystemPlanNodeOwner } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import type { SystemNodeExecutor, PluginNodeExecutionParams } from '../runtime/plugin-types'
|
|
4
|
+
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
5
|
+
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
6
|
+
|
|
7
|
+
const BUILT_IN_SYSTEM_EXECUTORS = Object.freeze({
|
|
8
|
+
'plan-runtime': {
|
|
9
|
+
supportedOperations: ['echo-input'],
|
|
10
|
+
async executeNode(params: PluginNodeExecutionParams): Promise<PlanNodeResult> {
|
|
11
|
+
return { structuredOutput: structuredClone(params.inputs), artifacts: [] }
|
|
12
|
+
},
|
|
13
|
+
} satisfies SystemNodeExecutor,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export function getBuiltInSystemExecutors(): Record<string, SystemNodeExecutor> {
|
|
17
|
+
return Object.fromEntries(Object.entries(BUILT_IN_SYSTEM_EXECUTORS))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getSystemExecutors() {
|
|
21
|
+
return (getRuntimeConfig().systemExecutors ?? getBuiltInSystemExecutors()) as Record<
|
|
22
|
+
string,
|
|
23
|
+
SystemNodeExecutor | undefined
|
|
24
|
+
>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildSystemExecutionParams(params: {
|
|
28
|
+
owner: SystemPlanNodeOwner
|
|
29
|
+
nodeSpec: PlanNodeSpec
|
|
30
|
+
resolvedInput: Record<string, unknown>
|
|
31
|
+
context: OwnershipDispatchContext
|
|
32
|
+
}): PluginNodeExecutionParams {
|
|
33
|
+
return {
|
|
34
|
+
operation: params.owner.operation,
|
|
35
|
+
nodeSpec: params.nodeSpec,
|
|
36
|
+
inputs: params.resolvedInput,
|
|
37
|
+
context: {
|
|
38
|
+
organizationId: params.context.organizationId,
|
|
39
|
+
workstreamId: params.context.workstreamId,
|
|
40
|
+
planId: params.context.planId,
|
|
41
|
+
nodeId: params.context.nodeId,
|
|
42
|
+
...(params.context.userId ? { userId: params.context.userId } : {}),
|
|
43
|
+
...(params.context.userName ? { userName: params.context.userName } : {}),
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class SystemExecutorService {
|
|
49
|
+
validateOwner(owner: SystemPlanNodeOwner, nodeId: string): PlanValidationIssueInput[] {
|
|
50
|
+
const executor = getSystemExecutors()[owner.ref]
|
|
51
|
+
if (!executor) {
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
severity: 'blocking',
|
|
55
|
+
code: 'system_executor_missing',
|
|
56
|
+
message: `Node "${nodeId}" references unknown system executor "${owner.ref}".`,
|
|
57
|
+
nodeId,
|
|
58
|
+
detail: { systemRef: owner.ref },
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!executor.supportedOperations.includes(owner.operation)) {
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
severity: 'blocking',
|
|
67
|
+
code: 'system_operation_missing',
|
|
68
|
+
message: `System executor "${owner.ref}" does not support operation "${owner.operation}".`,
|
|
69
|
+
nodeId,
|
|
70
|
+
detail: { systemRef: owner.ref, operation: owner.operation },
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async executeNode(params: {
|
|
79
|
+
nodeSpec: PlanNodeSpec
|
|
80
|
+
resolvedInput: Record<string, unknown>
|
|
81
|
+
context: OwnershipDispatchContext
|
|
82
|
+
}): Promise<PlanNodeResult> {
|
|
83
|
+
if (params.nodeSpec.owner.executorType !== 'system') {
|
|
84
|
+
throw new Error(`SystemExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const executor = getSystemExecutors()[params.nodeSpec.owner.ref]
|
|
88
|
+
if (!executor || !executor.supportedOperations.includes(params.nodeSpec.owner.operation)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`System executor ${params.nodeSpec.owner.ref}.${params.nodeSpec.owner.operation} is not registered.`,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return executor.executeNode(
|
|
95
|
+
buildSystemExecutionParams({
|
|
96
|
+
owner: params.nodeSpec.owner,
|
|
97
|
+
nodeSpec: params.nodeSpec,
|
|
98
|
+
resolvedInput: params.resolvedInput,
|
|
99
|
+
context: params.context,
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const systemExecutorService = new SystemExecutorService()
|