@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,293 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecutionMode,
|
|
3
|
+
OwnershipDispatchContext,
|
|
4
|
+
PlanArtifactSubmission,
|
|
5
|
+
PlanNodeResult,
|
|
6
|
+
PlanNodeSpec,
|
|
7
|
+
PlanSchemaRegistry,
|
|
8
|
+
WriteIntent,
|
|
9
|
+
} from '@lota-sdk/shared'
|
|
10
|
+
import { PlanNodeResultSubmissionSchema, WriteIntentSchema } from '@lota-sdk/shared'
|
|
11
|
+
import { stepCountIs, tool } from 'ai'
|
|
12
|
+
import type { ToolLoopAgent, ToolSet } from 'ai'
|
|
13
|
+
|
|
14
|
+
import { agentRoster, buildAgentTools, createAgent, getAgentRuntimeConfig } from '../config/agent-defaults'
|
|
15
|
+
import { ensureRecordId } from '../db/record-id'
|
|
16
|
+
import { databaseService } from '../db/service'
|
|
17
|
+
import { TABLES } from '../db/tables'
|
|
18
|
+
import {
|
|
19
|
+
OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES,
|
|
20
|
+
buildOwnershipDispatchContextSection,
|
|
21
|
+
buildOwnershipDispatchResponseGuard,
|
|
22
|
+
} from '../runtime/agent-runtime-policy'
|
|
23
|
+
import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
|
|
24
|
+
import { nodeWorkspaceService } from './node-workspace.service'
|
|
25
|
+
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
26
|
+
import { WorkstreamSchema } from './workstream.types'
|
|
27
|
+
import { writeIntentValidatorService } from './write-intent-validator.service'
|
|
28
|
+
|
|
29
|
+
function applyToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpec): ToolSet {
|
|
30
|
+
const blockedToolNames = new Set([...OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES, ...nodeSpec.toolPolicy.deny])
|
|
31
|
+
const allowList = nodeSpec.toolPolicy.allow.length > 0 ? new Set(nodeSpec.toolPolicy.allow) : null
|
|
32
|
+
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(tools).filter(
|
|
35
|
+
([toolName]) => !blockedToolNames.has(toolName) && (allowList === null || allowList.has(toolName)),
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildDispatchPrompt(nodeSpec: PlanNodeSpec): string {
|
|
41
|
+
return [
|
|
42
|
+
`Execute the execution-plan node "${nodeSpec.label}".`,
|
|
43
|
+
`Objective: ${nodeSpec.objective}`,
|
|
44
|
+
`Instructions: ${nodeSpec.instructions}`,
|
|
45
|
+
'Return the final node result JSON only.',
|
|
46
|
+
].join('\n\n')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildWriteIntentDispatchPrompt(nodeSpec: PlanNodeSpec): string {
|
|
50
|
+
const deliverables = nodeSpec.deliverables
|
|
51
|
+
.map((d) => `- ${d.name} (${d.kind}${d.required ? ', required' : ''})`)
|
|
52
|
+
.join('\n')
|
|
53
|
+
return [
|
|
54
|
+
`Execute the execution-plan node "${nodeSpec.label}".`,
|
|
55
|
+
`Objective: ${nodeSpec.objective}`,
|
|
56
|
+
`Instructions: ${nodeSpec.instructions}`,
|
|
57
|
+
'',
|
|
58
|
+
'Produce each deliverable by calling the writeIntent tool:',
|
|
59
|
+
deliverables,
|
|
60
|
+
'',
|
|
61
|
+
'For each, call writeIntent with targetPath matching the deliverable name.',
|
|
62
|
+
'If writeIntent returns validation_failed, correct the payload and try again.',
|
|
63
|
+
'When all deliverables are written, end with a brief completion summary.',
|
|
64
|
+
].join('\n')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const MAX_SELF_CORRECTION_RETRIES = 3
|
|
68
|
+
|
|
69
|
+
class AgentExecutorService {
|
|
70
|
+
validateOwner(agentId: string, nodeId: string): PlanValidationIssueInput[] {
|
|
71
|
+
if (!agentRoster.includes(agentId)) {
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
severity: 'blocking',
|
|
75
|
+
code: 'agent_executor_missing',
|
|
76
|
+
message: `Node "${nodeId}" references unknown agent executor "${agentId}".`,
|
|
77
|
+
nodeId,
|
|
78
|
+
detail: { agentId },
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return []
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async executeNode(params: {
|
|
87
|
+
nodeSpec: PlanNodeSpec
|
|
88
|
+
resolvedInput: Record<string, unknown>
|
|
89
|
+
inputArtifacts: PlanArtifactSubmission[]
|
|
90
|
+
context: OwnershipDispatchContext
|
|
91
|
+
executionMode?: ExecutionMode
|
|
92
|
+
schemaRegistry?: PlanSchemaRegistry
|
|
93
|
+
}): Promise<PlanNodeResult> {
|
|
94
|
+
if (params.nodeSpec.owner.executorType !== 'agent') {
|
|
95
|
+
throw new Error(`AgentExecutor cannot execute owner type "${params.nodeSpec.owner.executorType}".`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const agentId = params.nodeSpec.owner.ref
|
|
99
|
+
if (!agentRoster.includes(agentId)) {
|
|
100
|
+
throw new Error(`Agent executor "${agentId}" is not registered.`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const workstream = await databaseService.findOne(
|
|
104
|
+
TABLES.WORKSTREAM,
|
|
105
|
+
{ id: ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM) },
|
|
106
|
+
WorkstreamSchema,
|
|
107
|
+
)
|
|
108
|
+
if (!workstream) {
|
|
109
|
+
throw new Error(`Workstream ${params.context.workstreamId} not found for dispatched execution.`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
|
|
113
|
+
const workstreamRef = ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM)
|
|
114
|
+
const userRefSource = params.context.userId ?? workstream.userId
|
|
115
|
+
if (!userRefSource) {
|
|
116
|
+
throw new Error(`Workstream ${params.context.workstreamId} is missing a user context for dispatched execution.`)
|
|
117
|
+
}
|
|
118
|
+
const userRef = ensureRecordId(userRefSource, TABLES.USER)
|
|
119
|
+
const userName = params.context.userName ?? 'User'
|
|
120
|
+
const getLinearInstallationByOrgId = getPluginService([
|
|
121
|
+
'linear',
|
|
122
|
+
'services',
|
|
123
|
+
'linearService',
|
|
124
|
+
'getInstallationByOrgId',
|
|
125
|
+
])
|
|
126
|
+
const getGithubInstallationForOrganization = getPluginService([
|
|
127
|
+
'github',
|
|
128
|
+
'services',
|
|
129
|
+
'githubService',
|
|
130
|
+
'getInstallationForOrganization',
|
|
131
|
+
])
|
|
132
|
+
const [linearInstallation, githubInstallation, indexedRepoContext] = await Promise.all([
|
|
133
|
+
getLinearInstallationByOrgId ? getLinearInstallationByOrgId(organizationRef) : Promise.resolve(null),
|
|
134
|
+
getGithubInstallationForOrganization
|
|
135
|
+
? getGithubInstallationForOrganization(params.context.organizationId)
|
|
136
|
+
: Promise.resolve(null),
|
|
137
|
+
buildIndexedRepositoriesContext(params.context.organizationId),
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
const mode = params.executionMode ?? 'linear'
|
|
141
|
+
|
|
142
|
+
const dispatchMode = workstream.mode === 'group' ? 'fixedWorkstreamMode' : 'direct'
|
|
143
|
+
const runtimeConfig = getAgentRuntimeConfig({
|
|
144
|
+
agentId,
|
|
145
|
+
workstreamMode: workstream.mode,
|
|
146
|
+
mode: dispatchMode,
|
|
147
|
+
onboardingActive: false,
|
|
148
|
+
linearInstalled: Boolean(linearInstallation),
|
|
149
|
+
reasoningProfile: 'standard',
|
|
150
|
+
additionalInstructionSections: [
|
|
151
|
+
buildOwnershipDispatchContextSection({
|
|
152
|
+
node: params.nodeSpec,
|
|
153
|
+
resolvedInput: params.resolvedInput,
|
|
154
|
+
inputArtifacts: params.inputArtifacts,
|
|
155
|
+
}),
|
|
156
|
+
],
|
|
157
|
+
responseGuardSection: buildOwnershipDispatchResponseGuard({ node: params.nodeSpec, executionMode: mode }),
|
|
158
|
+
}) as Record<string, unknown>
|
|
159
|
+
|
|
160
|
+
const rawTools = (await buildAgentTools({
|
|
161
|
+
agentId,
|
|
162
|
+
orgId: organizationRef,
|
|
163
|
+
userId: userRef,
|
|
164
|
+
userName,
|
|
165
|
+
workstreamId: workstreamRef,
|
|
166
|
+
orgIdString: params.context.organizationId,
|
|
167
|
+
workstreamMode: workstream.mode,
|
|
168
|
+
mode: dispatchMode,
|
|
169
|
+
linearInstalled: Boolean(linearInstallation),
|
|
170
|
+
onboardingActive: false,
|
|
171
|
+
githubInstalled: Boolean(githubInstallation),
|
|
172
|
+
provideRepoTool: indexedRepoContext.provideRepoTool,
|
|
173
|
+
defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[agentId],
|
|
174
|
+
memoryBlock: '',
|
|
175
|
+
onAppendMemoryBlock: () => undefined,
|
|
176
|
+
availableUploads: [],
|
|
177
|
+
includeExecutionPlanTools: false,
|
|
178
|
+
})) as ToolSet
|
|
179
|
+
const tools = applyToolPolicy(rawTools, params.nodeSpec)
|
|
180
|
+
|
|
181
|
+
const agentFactory = createAgent[agentId] as ((...args: unknown[]) => unknown) | undefined
|
|
182
|
+
if (!agentFactory) {
|
|
183
|
+
throw new Error(`Agent factory "${agentId}" is not registered.`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const maxSteps = typeof runtimeConfig.maxSteps === 'number' ? runtimeConfig.maxSteps : 8
|
|
187
|
+
|
|
188
|
+
if (mode === 'linear') {
|
|
189
|
+
const agent = agentFactory({
|
|
190
|
+
mode: dispatchMode,
|
|
191
|
+
tools,
|
|
192
|
+
extraInstructions:
|
|
193
|
+
typeof runtimeConfig.extraInstructions === 'string' ? runtimeConfig.extraInstructions : undefined,
|
|
194
|
+
maxRetries: 1,
|
|
195
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
196
|
+
}) as ToolLoopAgent<never, ToolSet>
|
|
197
|
+
|
|
198
|
+
const result = await agent.generate({ prompt: buildDispatchPrompt(params.nodeSpec) })
|
|
199
|
+
const outputCandidate = PlanNodeResultSubmissionSchema.safeParse(result.output)
|
|
200
|
+
if (outputCandidate.success) {
|
|
201
|
+
return outputCandidate.data
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw new Error(`Agent executor "${agentId}" returned an invalid node result.`)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Graph-lite / Graph-full path ---
|
|
208
|
+
|
|
209
|
+
const workspace = nodeWorkspaceService.initialize({
|
|
210
|
+
nodeSpec: params.nodeSpec,
|
|
211
|
+
resolvedInput: params.resolvedInput,
|
|
212
|
+
inputArtifacts: params.inputArtifacts,
|
|
213
|
+
schemaRegistry: params.schemaRegistry ?? {},
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const writeIntentTool = tool({
|
|
217
|
+
description:
|
|
218
|
+
'Write a validated artifact or structured output field. Call this for each deliverable. If validation fails, correct your payload and try again.',
|
|
219
|
+
inputSchema: WriteIntentSchema,
|
|
220
|
+
execute: async (intent: WriteIntent) => {
|
|
221
|
+
const correctionCount = workspace.sys.correctionCounts.get(intent.targetPath) ?? 0
|
|
222
|
+
|
|
223
|
+
const validation = writeIntentValidatorService.validate({
|
|
224
|
+
intent,
|
|
225
|
+
nodeSpec: params.nodeSpec,
|
|
226
|
+
schemaRegistry: workspace.ctx.schemaRegistry,
|
|
227
|
+
existingDeliverables: workspace.deliverables,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (validation.issues.length > 0) {
|
|
231
|
+
if (correctionCount >= MAX_SELF_CORRECTION_RETRIES) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Write validation failed for "${intent.targetPath}" after ${MAX_SELF_CORRECTION_RETRIES} retries: ${validation.issues.map((i) => i.message).join(', ')}`,
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
workspace.sys.correctionCounts.set(intent.targetPath, correctionCount + 1)
|
|
237
|
+
workspace.sys.writeLog.push({
|
|
238
|
+
targetPath: intent.targetPath,
|
|
239
|
+
action: intent.action,
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
result: 'rejected',
|
|
242
|
+
})
|
|
243
|
+
return {
|
|
244
|
+
status: 'validation_failed',
|
|
245
|
+
error: { targetPath: intent.targetPath, action: intent.action, issues: validation.issues },
|
|
246
|
+
hint: `Correct and re-emit. Attempt ${correctionCount + 1}/${MAX_SELF_CORRECTION_RETRIES}.`,
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
nodeWorkspaceService.stageWrite(workspace, intent, 'validated')
|
|
251
|
+
return { status: 'accepted', message: `Write to "${intent.targetPath}" validated and staged.` }
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const graphTools = { ...tools, writeIntent: writeIntentTool }
|
|
256
|
+
|
|
257
|
+
const agent = agentFactory({
|
|
258
|
+
mode: dispatchMode,
|
|
259
|
+
tools: graphTools,
|
|
260
|
+
extraInstructions:
|
|
261
|
+
typeof runtimeConfig.extraInstructions === 'string' ? runtimeConfig.extraInstructions : undefined,
|
|
262
|
+
maxRetries: 1,
|
|
263
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
264
|
+
}) as ToolLoopAgent<never, ToolSet>
|
|
265
|
+
|
|
266
|
+
await agent.generate({ prompt: buildWriteIntentDispatchPrompt(params.nodeSpec) })
|
|
267
|
+
|
|
268
|
+
const finalResult = nodeWorkspaceService.finalize(workspace)
|
|
269
|
+
|
|
270
|
+
if (!finalResult.isComplete) {
|
|
271
|
+
const result: PlanNodeResult = {
|
|
272
|
+
structuredOutput: finalResult.structuredOutput,
|
|
273
|
+
artifacts: finalResult.artifacts,
|
|
274
|
+
notes: 'Execution incomplete: missing required deliverables or validation failures.',
|
|
275
|
+
quality: 'partial',
|
|
276
|
+
}
|
|
277
|
+
nodeWorkspaceService.cleanup(workspace)
|
|
278
|
+
return result
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const result: PlanNodeResult = {
|
|
282
|
+
structuredOutput: finalResult.structuredOutput,
|
|
283
|
+
artifacts: finalResult.artifacts,
|
|
284
|
+
notes: finalResult.notes,
|
|
285
|
+
quality: finalResult.quality,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
nodeWorkspaceService.cleanup(workspace)
|
|
289
|
+
return result
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export const agentExecutorService = new AgentExecutorService()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { ImpactAnalysis, PlanNodeRunRecord, ProvenanceChain, ProvenanceNode } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { recordIdToString } from '../db/record-id'
|
|
4
|
+
|
|
5
|
+
class ArtifactProvenanceService {
|
|
6
|
+
/**
|
|
7
|
+
* Backward: trace which upstream nodes/artifacts produced the inputs for this artifact's node.
|
|
8
|
+
* From nodeId, follow edges upstream. At each upstream node, collect its artifacts.
|
|
9
|
+
* Continue until maxDepth or no more upstreams.
|
|
10
|
+
*/
|
|
11
|
+
async traceBackward(params: { runId: string; nodeId: string; maxDepth?: number }): Promise<ProvenanceChain> {
|
|
12
|
+
const { planRunService } = await import('./plan-run.service')
|
|
13
|
+
|
|
14
|
+
const run = await planRunService.getRunById(params.runId)
|
|
15
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
16
|
+
const nodeSpecs = await planRunService.listNodeSpecs(spec.id)
|
|
17
|
+
const artifacts = await planRunService.listArtifacts(run.id)
|
|
18
|
+
|
|
19
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((n) => [n.nodeId, n]))
|
|
20
|
+
const artifactsByNodeId = new Map<string, typeof artifacts>()
|
|
21
|
+
for (const artifact of artifacts) {
|
|
22
|
+
const list = artifactsByNodeId.get(artifact.nodeId) ?? []
|
|
23
|
+
list.push(artifact)
|
|
24
|
+
artifactsByNodeId.set(artifact.nodeId, list)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const maxDepth = params.maxDepth ?? 10
|
|
28
|
+
const chain: ProvenanceNode[] = []
|
|
29
|
+
const visited = new Set<string>()
|
|
30
|
+
const queue: Array<{ nodeId: string; depth: number }> = [{ nodeId: params.nodeId, depth: 0 }]
|
|
31
|
+
|
|
32
|
+
while (queue.length > 0) {
|
|
33
|
+
const entry = queue.shift()
|
|
34
|
+
if (!entry || visited.has(entry.nodeId) || entry.depth > maxDepth) continue
|
|
35
|
+
visited.add(entry.nodeId)
|
|
36
|
+
|
|
37
|
+
const nodeSpec = nodeSpecsByNodeId.get(entry.nodeId)
|
|
38
|
+
if (!nodeSpec) continue
|
|
39
|
+
|
|
40
|
+
const nodeArtifacts = (artifactsByNodeId.get(entry.nodeId) ?? []).map((a) => ({
|
|
41
|
+
name: a.name,
|
|
42
|
+
kind: a.kind,
|
|
43
|
+
id: recordIdToString(a.id),
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
chain.push({ nodeId: entry.nodeId, label: nodeSpec.label, artifacts: nodeArtifacts })
|
|
47
|
+
|
|
48
|
+
// Walk upstream
|
|
49
|
+
for (const upstreamId of nodeSpec.upstreamNodeIds) {
|
|
50
|
+
if (!visited.has(upstreamId)) {
|
|
51
|
+
queue.push({ nodeId: upstreamId, depth: entry.depth + 1 })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { root: { nodeId: params.nodeId }, chain, depth: Math.max(0, chain.length - 1) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Forward: trace which downstream nodes consume this artifact's node output.
|
|
61
|
+
* Same as backward but follow edges downstream.
|
|
62
|
+
*/
|
|
63
|
+
async traceForward(params: { runId: string; nodeId: string; maxDepth?: number }): Promise<ProvenanceChain> {
|
|
64
|
+
const { planRunService } = await import('./plan-run.service')
|
|
65
|
+
|
|
66
|
+
const run = await planRunService.getRunById(params.runId)
|
|
67
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
68
|
+
const nodeSpecs = await planRunService.listNodeSpecs(spec.id)
|
|
69
|
+
const artifacts = await planRunService.listArtifacts(run.id)
|
|
70
|
+
|
|
71
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((n) => [n.nodeId, n]))
|
|
72
|
+
const artifactsByNodeId = new Map<string, typeof artifacts>()
|
|
73
|
+
for (const artifact of artifacts) {
|
|
74
|
+
const list = artifactsByNodeId.get(artifact.nodeId) ?? []
|
|
75
|
+
list.push(artifact)
|
|
76
|
+
artifactsByNodeId.set(artifact.nodeId, list)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const maxDepth = params.maxDepth ?? 10
|
|
80
|
+
const chain: ProvenanceNode[] = []
|
|
81
|
+
const visited = new Set<string>()
|
|
82
|
+
const queue: Array<{ nodeId: string; depth: number }> = [{ nodeId: params.nodeId, depth: 0 }]
|
|
83
|
+
|
|
84
|
+
while (queue.length > 0) {
|
|
85
|
+
const entry = queue.shift()
|
|
86
|
+
if (!entry || visited.has(entry.nodeId) || entry.depth > maxDepth) continue
|
|
87
|
+
visited.add(entry.nodeId)
|
|
88
|
+
|
|
89
|
+
const nodeSpec = nodeSpecsByNodeId.get(entry.nodeId)
|
|
90
|
+
if (!nodeSpec) continue
|
|
91
|
+
|
|
92
|
+
const nodeArtifacts = (artifactsByNodeId.get(entry.nodeId) ?? []).map((a) => ({
|
|
93
|
+
name: a.name,
|
|
94
|
+
kind: a.kind,
|
|
95
|
+
id: recordIdToString(a.id),
|
|
96
|
+
}))
|
|
97
|
+
|
|
98
|
+
chain.push({ nodeId: entry.nodeId, label: nodeSpec.label, artifacts: nodeArtifacts })
|
|
99
|
+
|
|
100
|
+
// Walk downstream
|
|
101
|
+
for (const downstreamId of nodeSpec.downstreamNodeIds) {
|
|
102
|
+
if (!visited.has(downstreamId)) {
|
|
103
|
+
queue.push({ nodeId: downstreamId, depth: entry.depth + 1 })
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { root: { nodeId: params.nodeId }, chain, depth: Math.max(0, chain.length - 1) }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Impact: which downstream nodes/artifacts would be affected.
|
|
113
|
+
* Forward traversal collecting all affected nodes and their artifacts.
|
|
114
|
+
* Includes non-completed nodes.
|
|
115
|
+
*/
|
|
116
|
+
async analyzeImpact(params: { runId: string; nodeId: string }): Promise<ImpactAnalysis> {
|
|
117
|
+
const { planRunService } = await import('./plan-run.service')
|
|
118
|
+
|
|
119
|
+
const run = await planRunService.getRunById(params.runId)
|
|
120
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
121
|
+
const nodeSpecs = await planRunService.listNodeSpecs(spec.id)
|
|
122
|
+
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
123
|
+
const artifacts = await planRunService.listArtifacts(run.id)
|
|
124
|
+
|
|
125
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((n) => [n.nodeId, n]))
|
|
126
|
+
const nodeRunsByNodeId = new Map(nodeRuns.map((n: PlanNodeRunRecord) => [n.nodeId, n]))
|
|
127
|
+
|
|
128
|
+
const affectedNodes: Array<{ nodeId: string; label: string; status: PlanNodeRunRecord['status'] }> = []
|
|
129
|
+
const affectedArtifacts: Array<{ name: string; nodeId: string }> = []
|
|
130
|
+
|
|
131
|
+
const visited = new Set<string>()
|
|
132
|
+
const queue: string[] = []
|
|
133
|
+
|
|
134
|
+
// Seed: downstream neighbors of the source node
|
|
135
|
+
const sourceSpec = nodeSpecsByNodeId.get(params.nodeId)
|
|
136
|
+
if (sourceSpec) {
|
|
137
|
+
for (const downstreamId of sourceSpec.downstreamNodeIds) {
|
|
138
|
+
queue.push(downstreamId)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
while (queue.length > 0) {
|
|
143
|
+
const nodeId = queue.shift()
|
|
144
|
+
if (!nodeId || visited.has(nodeId)) continue
|
|
145
|
+
visited.add(nodeId)
|
|
146
|
+
|
|
147
|
+
const nodeSpec = nodeSpecsByNodeId.get(nodeId)
|
|
148
|
+
if (!nodeSpec) continue
|
|
149
|
+
|
|
150
|
+
const nodeRun = nodeRunsByNodeId.get(nodeId)
|
|
151
|
+
affectedNodes.push({ nodeId, label: nodeSpec.label, status: nodeRun?.status ?? 'pending' })
|
|
152
|
+
|
|
153
|
+
// Collect artifacts for this node
|
|
154
|
+
for (const artifact of artifacts.filter((a) => a.nodeId === nodeId)) {
|
|
155
|
+
affectedArtifacts.push({ name: artifact.name, nodeId })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Continue downstream
|
|
159
|
+
for (const downstreamId of nodeSpec.downstreamNodeIds) {
|
|
160
|
+
if (!visited.has(downstreamId)) queue.push(downstreamId)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
sourceNodeId: params.nodeId,
|
|
166
|
+
affectedNodes: affectedNodes as ImpactAnalysis['affectedNodes'],
|
|
167
|
+
affectedArtifacts,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const artifactProvenanceService = new ArtifactProvenanceService()
|
|
@@ -3,7 +3,7 @@ import { recordIdToString } from '../db/record-id'
|
|
|
3
3
|
import { TABLES } from '../db/tables'
|
|
4
4
|
import { attachmentStorageService } from '../storage/attachment-storage.service'
|
|
5
5
|
import type { UploadedWorkstreamAttachment as SdkUploadedWorkstreamAttachment } from '../storage/attachment-storage.service'
|
|
6
|
-
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/
|
|
6
|
+
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
|
|
7
7
|
|
|
8
8
|
export type ReadableUploadMetadata = SdkReadableUploadMetadata
|
|
9
9
|
|
|
@@ -57,7 +57,7 @@ class AttachmentService {
|
|
|
57
57
|
name: string
|
|
58
58
|
contentType: string
|
|
59
59
|
}): Promise<string> {
|
|
60
|
-
return
|
|
60
|
+
return attachmentStorageService.extractStoredAttachmentText({ storageKey, name, contentType })
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async extractStoredAttachmentPages({
|
|
@@ -69,7 +69,7 @@ class AttachmentService {
|
|
|
69
69
|
name: string
|
|
70
70
|
contentType: string
|
|
71
71
|
}): Promise<{ pageMode: 'logical' | 'pdf'; pages: string[] }> {
|
|
72
|
-
return
|
|
72
|
+
return attachmentStorageService.extractStoredAttachmentPages({ storageKey, name, contentType })
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
async readFilePartsFromUpload({
|
|
@@ -85,7 +85,7 @@ class AttachmentService {
|
|
|
85
85
|
part?: number
|
|
86
86
|
pagesPerPart?: number
|
|
87
87
|
}) {
|
|
88
|
-
return
|
|
88
|
+
return attachmentStorageService.readFilePartsFromUpload({
|
|
89
89
|
upload,
|
|
90
90
|
orgId: toOrgId(orgId),
|
|
91
91
|
userId: toUserId(userId),
|
|
@@ -111,7 +111,7 @@ class AttachmentService {
|
|
|
111
111
|
content: string
|
|
112
112
|
contentType: string
|
|
113
113
|
}): Promise<{ storageKey: string; sizeBytes: number }> {
|
|
114
|
-
return
|
|
114
|
+
return attachmentStorageService.writeOrganizationDocument({
|
|
115
115
|
orgId: toOrgId(orgId),
|
|
116
116
|
namespace,
|
|
117
117
|
relativePath,
|
|
@@ -131,12 +131,7 @@ class AttachmentService {
|
|
|
131
131
|
namespace: string
|
|
132
132
|
relativePath: string
|
|
133
133
|
}) {
|
|
134
|
-
return
|
|
135
|
-
file,
|
|
136
|
-
orgId: toOrgId(orgId),
|
|
137
|
-
namespace,
|
|
138
|
-
relativePath,
|
|
139
|
-
})
|
|
134
|
+
return attachmentStorageService.uploadOrganizationDocument({ file, orgId: toOrgId(orgId), namespace, relativePath })
|
|
140
135
|
}
|
|
141
136
|
|
|
142
137
|
async uploadWorkstreamAttachment({
|
|
@@ -148,7 +143,7 @@ class AttachmentService {
|
|
|
148
143
|
orgId: RecordIdRef
|
|
149
144
|
userId: RecordIdRef
|
|
150
145
|
}): Promise<SdkUploadedWorkstreamAttachment> {
|
|
151
|
-
return
|
|
146
|
+
return attachmentStorageService.uploadWorkstreamAttachment({
|
|
152
147
|
file,
|
|
153
148
|
orgId: toOrgId(orgId),
|
|
154
149
|
userId: toUserId(userId),
|
|
@@ -5,10 +5,12 @@ import type { RecordIdRef } from '../db/record-id'
|
|
|
5
5
|
import { recordIdToString } from '../db/record-id'
|
|
6
6
|
import { databaseService } from '../db/service'
|
|
7
7
|
import { TABLES } from '../db/tables'
|
|
8
|
+
import { getRedisConnection } from '../redis/connection-accessor'
|
|
9
|
+
import { withRedisLeaseLock } from '../redis/redis-lease-lock'
|
|
8
10
|
import { parseWorkstreamState, toStateFieldsUpdated } from '../runtime/context-compaction'
|
|
9
|
-
import {
|
|
11
|
+
import { CONTEXT_WINDOW_TOKENS, WORKSTREAM_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
|
|
10
12
|
import type { WorkstreamState } from '../runtime/workstream-state'
|
|
11
|
-
import { compactMemoryBlockSummary, contextCompactionRuntime } from './context-compaction-runtime'
|
|
13
|
+
import { compactMemoryBlockSummary, contextCompactionRuntime } from './context-compaction-runtime.singleton'
|
|
12
14
|
import { workstreamMessageService } from './workstream-message.service'
|
|
13
15
|
import { WorkstreamSchema } from './workstream.types'
|
|
14
16
|
|
|
@@ -35,7 +37,7 @@ class ContextCompactionService {
|
|
|
35
37
|
return contextCompactionRuntime.formatWorkstreamStateForPrompt(state)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
estimateThreshold(contextSize =
|
|
40
|
+
estimateThreshold(contextSize = CONTEXT_WINDOW_TOKENS): number {
|
|
39
41
|
return contextCompactionRuntime.estimateThreshold(contextSize)
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -47,68 +49,83 @@ class ContextCompactionService {
|
|
|
47
49
|
workstreamId: RecordIdRef
|
|
48
50
|
contextSize?: number
|
|
49
51
|
}): Promise<{ compacted: boolean; state: WorkstreamState | null }> {
|
|
50
|
-
const
|
|
51
|
-
if (!workstream) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
`Workstream not found for compaction: ${recordIdToString(params.workstreamId, TABLES.WORKSTREAM)}`,
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const currentState = parseWorkstreamState(workstream.state)
|
|
58
|
-
const liveMessages = await workstreamMessageService.listMessagesAfterCursor(
|
|
59
|
-
params.workstreamId,
|
|
60
|
-
typeof workstream.lastCompactedMessageId === 'string' ? workstream.lastCompactedMessageId : undefined,
|
|
61
|
-
)
|
|
52
|
+
const entityId = recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
summaryText: typeof workstream.compactionSummary === 'string' ? workstream.compactionSummary : '',
|
|
65
|
-
liveMessages,
|
|
66
|
-
tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
|
|
67
|
-
contextSize: params.contextSize,
|
|
68
|
-
existingState: currentState,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
if (!result.compacted || !result.lastCompactedMessageId) {
|
|
72
|
-
return { compacted: false, state: currentState }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (result.compactedMessages.length > 0) {
|
|
76
|
-
await workstreamMessageService.upsertMessages({
|
|
77
|
-
workstreamId: params.workstreamId,
|
|
78
|
-
messages: result.compactedMessages,
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
await databaseService.update(
|
|
83
|
-
TABLES.WORKSTREAM,
|
|
84
|
-
params.workstreamId,
|
|
54
|
+
return withRedisLeaseLock(
|
|
85
55
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
56
|
+
redis: getRedisConnection(),
|
|
57
|
+
lockKey: `compaction:lock:${entityId}`,
|
|
58
|
+
lockTtlMs: 120_000,
|
|
59
|
+
maxWaitMs: 30_000,
|
|
60
|
+
label: 'context-compaction',
|
|
61
|
+
},
|
|
62
|
+
async () => {
|
|
63
|
+
const workstream = await databaseService.findOne(
|
|
64
|
+
TABLES.WORKSTREAM,
|
|
65
|
+
{ id: params.workstreamId },
|
|
66
|
+
WorkstreamSchema,
|
|
67
|
+
)
|
|
68
|
+
if (!workstream) {
|
|
69
|
+
throw new Error(`Workstream not found for compaction: ${entityId}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const currentState = parseWorkstreamState(workstream.state)
|
|
73
|
+
const liveMessages = await workstreamMessageService.listMessagesAfterCursor(
|
|
74
|
+
params.workstreamId,
|
|
75
|
+
typeof workstream.lastCompactedMessageId === 'string' ? workstream.lastCompactedMessageId : undefined,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const result = await contextCompactionRuntime.compactHistory({
|
|
79
|
+
summaryText: typeof workstream.compactionSummary === 'string' ? workstream.compactionSummary : '',
|
|
80
|
+
liveMessages,
|
|
81
|
+
tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
|
|
82
|
+
contextSize: params.contextSize,
|
|
83
|
+
existingState: currentState,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (!result.compacted || !result.lastCompactedMessageId) {
|
|
87
|
+
return { compacted: false, state: currentState }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (result.compactedMessages.length > 0) {
|
|
91
|
+
await workstreamMessageService.upsertMessages({
|
|
92
|
+
workstreamId: params.workstreamId,
|
|
93
|
+
messages: result.compactedMessages,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await databaseService.update(
|
|
98
|
+
TABLES.WORKSTREAM,
|
|
99
|
+
params.workstreamId,
|
|
100
|
+
{
|
|
101
|
+
compactionSummary: result.summaryText,
|
|
102
|
+
lastCompactedMessageId: result.lastCompactedMessageId,
|
|
103
|
+
state: result.state,
|
|
104
|
+
},
|
|
105
|
+
WorkstreamSchema,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
this.logCompactionMetrics({
|
|
109
|
+
domain: 'workstream',
|
|
110
|
+
entityId,
|
|
111
|
+
inputChars: result.inputChars,
|
|
112
|
+
outputChars: result.outputChars,
|
|
113
|
+
savedChars: Math.max(0, result.inputChars - result.outputChars),
|
|
114
|
+
summaryLength: result.summaryText.length,
|
|
115
|
+
compactedMessageCount: result.compactedMessageCount,
|
|
116
|
+
remainingMessageCount: result.remainingMessageCount,
|
|
117
|
+
estimatedTokens: result.estimatedTokens,
|
|
118
|
+
stateFieldsUpdated: toStateFieldsUpdated(result.stateDelta),
|
|
119
|
+
conflictsDetected: result.stateDelta.conflicts?.length ?? 0,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return { compacted: true, state: result.state }
|
|
89
123
|
},
|
|
90
|
-
WorkstreamSchema,
|
|
91
124
|
)
|
|
92
|
-
|
|
93
|
-
this.logCompactionMetrics({
|
|
94
|
-
domain: 'workstream',
|
|
95
|
-
entityId: recordIdToString(params.workstreamId, TABLES.WORKSTREAM),
|
|
96
|
-
inputChars: result.inputChars,
|
|
97
|
-
outputChars: result.outputChars,
|
|
98
|
-
savedChars: Math.max(0, result.inputChars - result.outputChars),
|
|
99
|
-
summaryLength: result.summaryText.length,
|
|
100
|
-
compactedMessageCount: result.compactedMessageCount,
|
|
101
|
-
remainingMessageCount: result.remainingMessageCount,
|
|
102
|
-
estimatedTokens: result.estimatedTokens,
|
|
103
|
-
stateFieldsUpdated: toStateFieldsUpdated(result.stateDelta),
|
|
104
|
-
conflictsDetected: result.stateDelta.conflicts?.length ?? 0,
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
return { compacted: true, state: result.state }
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
async compactMemoryBlock(params: { previousSummary: string; newEntriesText: string }): Promise<string> {
|
|
111
|
-
return
|
|
128
|
+
return compactMemoryBlockSummary(params)
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
private logCompactionMetrics(metrics: PersistedCompactionMetrics): void {
|