@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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ExecutionMode, PlanArtifactSubmission, PlanNodeSpec } from '@lota-sdk/shared'
|
|
2
|
+
|
|
1
3
|
import { getLeadAgentId } from '../config/agent-defaults'
|
|
2
4
|
import { resolveOnboardingOwnerAgentId } from '../config/workstream-defaults'
|
|
3
5
|
import type { ChatMode } from './agent-types'
|
|
@@ -31,6 +33,30 @@ export interface AgentToolPolicy<TSkill extends PropertyKey> {
|
|
|
31
33
|
includeIndexedRepository: boolean
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
export const OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES = Object.freeze([
|
|
37
|
+
'conversationSearch',
|
|
38
|
+
'createExecutionPlan',
|
|
39
|
+
'replaceExecutionPlan',
|
|
40
|
+
'submitExecutionNodeResult',
|
|
41
|
+
'listExecutionPlans',
|
|
42
|
+
'getExecutionPlanDetails',
|
|
43
|
+
'resumeExecutionPlanRun',
|
|
44
|
+
'consultSpecialist',
|
|
45
|
+
'consultTeam',
|
|
46
|
+
'teamThink',
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
function buildOwnershipDispatchArtifactPayload(artifacts: PlanArtifactSubmission[]) {
|
|
50
|
+
return artifacts.map((artifact) => ({
|
|
51
|
+
name: artifact.name,
|
|
52
|
+
kind: artifact.kind,
|
|
53
|
+
pointer: artifact.pointer,
|
|
54
|
+
...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
|
|
55
|
+
...(artifact.description ? { description: artifact.description } : {}),
|
|
56
|
+
...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
export function toChatMode(workstreamMode: 'direct' | 'group'): ChatMode {
|
|
35
61
|
return workstreamMode === 'direct' ? 'fixedWorkstreamMode' : 'workstreamMode'
|
|
36
62
|
}
|
|
@@ -160,37 +186,96 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
|
|
|
160
186
|
}
|
|
161
187
|
}
|
|
162
188
|
|
|
163
|
-
export function buildTeamConsultationAgentToolPolicy
|
|
164
|
-
|
|
165
|
-
|
|
189
|
+
export function buildTeamConsultationAgentToolPolicy({
|
|
190
|
+
githubInstalled,
|
|
191
|
+
provideRepoTool,
|
|
192
|
+
blockedToolNames,
|
|
193
|
+
}: {
|
|
166
194
|
blockedToolNames: readonly string[]
|
|
167
195
|
githubInstalled: boolean
|
|
168
196
|
provideRepoTool: boolean
|
|
169
|
-
|
|
170
|
-
}): AgentToolPolicy<TSkill> & { blockedToolNames: Set<string> } {
|
|
171
|
-
const skills = resolveActiveAgentSkills({
|
|
172
|
-
agentId: params.agentId,
|
|
173
|
-
workstreamMode: 'group',
|
|
174
|
-
mode: 'fixedWorkstreamMode',
|
|
175
|
-
onboardingActive: false,
|
|
176
|
-
linearInstalled: false,
|
|
177
|
-
getAgentSkills: params.getAgentSkills,
|
|
178
|
-
}).filter((skill) => !params.blockedSkills.includes(skill))
|
|
179
|
-
|
|
197
|
+
}): AgentToolPolicy<string> & { blockedToolNames: Set<string> } {
|
|
180
198
|
return {
|
|
181
199
|
resolvedMode: 'fixedWorkstreamMode',
|
|
182
|
-
skills,
|
|
183
|
-
includeMemorySearch:
|
|
184
|
-
includeConversationSearch:
|
|
200
|
+
skills: [],
|
|
201
|
+
includeMemorySearch: false,
|
|
202
|
+
includeConversationSearch: false,
|
|
185
203
|
includeMemoryRemember: false,
|
|
186
|
-
includeOrgActionSearch:
|
|
204
|
+
includeOrgActionSearch: false,
|
|
187
205
|
includeMemoryBlockAppend: false,
|
|
188
|
-
includeReadFileParts:
|
|
206
|
+
includeReadFileParts: false,
|
|
189
207
|
includeInspectWebsite: false,
|
|
190
208
|
includeProceedInOnboarding: false,
|
|
191
209
|
includeGithubIntegration: false,
|
|
192
210
|
includeIndexRepositoryByURL: false,
|
|
193
|
-
includeIndexedRepository:
|
|
194
|
-
blockedToolNames: new Set(
|
|
211
|
+
includeIndexedRepository: githubInstalled && provideRepoTool,
|
|
212
|
+
blockedToolNames: new Set(blockedToolNames),
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function buildOwnershipDispatchContextSection(params: {
|
|
217
|
+
node: PlanNodeSpec
|
|
218
|
+
resolvedInput: Record<string, unknown>
|
|
219
|
+
inputArtifacts: PlanArtifactSubmission[]
|
|
220
|
+
}): string {
|
|
221
|
+
const payload = {
|
|
222
|
+
node: {
|
|
223
|
+
id: params.node.id,
|
|
224
|
+
label: params.node.label,
|
|
225
|
+
owner: params.node.owner,
|
|
226
|
+
objective: params.node.objective,
|
|
227
|
+
instructions: params.node.instructions,
|
|
228
|
+
outputSchemaRef: params.node.outputSchemaRef ?? null,
|
|
229
|
+
deliverables: params.node.deliverables,
|
|
230
|
+
successCriteria: params.node.successCriteria,
|
|
231
|
+
completionChecks: params.node.completionChecks,
|
|
232
|
+
toolPolicy: params.node.toolPolicy,
|
|
233
|
+
contextPolicy: params.node.contextPolicy,
|
|
234
|
+
},
|
|
235
|
+
resolvedInput: params.resolvedInput,
|
|
236
|
+
inputArtifacts: buildOwnershipDispatchArtifactPayload(params.inputArtifacts),
|
|
195
237
|
}
|
|
238
|
+
|
|
239
|
+
return [
|
|
240
|
+
'<ownership-dispatch-execution>',
|
|
241
|
+
'You are executing a single isolated execution-plan node.',
|
|
242
|
+
'Do not ask the user questions. Do not reference any hidden or prior workstream chat history.',
|
|
243
|
+
'Use only the provided node context, resolved input, and input artifacts.',
|
|
244
|
+
'Return only the final structured node result that satisfies the required output contract.',
|
|
245
|
+
JSON.stringify(payload, null, 2),
|
|
246
|
+
'</ownership-dispatch-execution>',
|
|
247
|
+
].join('\n')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function buildOwnershipDispatchResponseGuard(params: {
|
|
251
|
+
node: PlanNodeSpec
|
|
252
|
+
executionMode?: ExecutionMode
|
|
253
|
+
}): string {
|
|
254
|
+
const mode = params.executionMode ?? 'linear'
|
|
255
|
+
|
|
256
|
+
if (mode === 'linear') {
|
|
257
|
+
return [
|
|
258
|
+
'<ownership-dispatch-result-contract>',
|
|
259
|
+
'Return a single JSON object with this exact shape:',
|
|
260
|
+
'{"structuredOutput"?: object, "artifacts": Array<{ "name": string, "kind": "json"|"markdown"|"file"|"external-ref"|"record", "pointer": string, "schemaRef"?: string, "description"?: string, "payload"?: object|array }>, "notes"?: string}',
|
|
261
|
+
'Do not wrap the JSON in markdown or code fences.',
|
|
262
|
+
`Node label: ${params.node.label}`,
|
|
263
|
+
`Required deliverables: ${params.node.deliverables.length > 0 ? params.node.deliverables.map((item) => item.name).join(', ') : 'none'}`,
|
|
264
|
+
'</ownership-dispatch-result-contract>',
|
|
265
|
+
].join('\n')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return [
|
|
269
|
+
'<ownership-dispatch-result-contract>',
|
|
270
|
+
'Produce outputs by calling the writeIntent tool for each deliverable.',
|
|
271
|
+
`Required deliverables: ${
|
|
272
|
+
params.node.deliverables
|
|
273
|
+
.filter((d) => d.required)
|
|
274
|
+
.map((d) => d.name)
|
|
275
|
+
.join(', ') || 'none'
|
|
276
|
+
}`,
|
|
277
|
+
'If writeIntent returns validation_failed, correct and re-call.',
|
|
278
|
+
'After all writes, return a brief summary.',
|
|
279
|
+
'</ownership-dispatch-result-contract>',
|
|
280
|
+
].join('\n')
|
|
196
281
|
}
|
|
@@ -2,13 +2,14 @@ import type { ChatMessage } from '@lota-sdk/shared'
|
|
|
2
2
|
import type { LanguageModelUsage, UIMessageStreamOptions } from 'ai'
|
|
3
3
|
|
|
4
4
|
import { agentDisplayNames, getLeadAgentDisplayName } from '../config/agent-defaults'
|
|
5
|
+
import { readRecord as _readRecord } from '../utils/string'
|
|
5
6
|
|
|
6
7
|
export function readFiniteNumber(value: unknown): number | undefined {
|
|
7
8
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
11
|
-
return
|
|
12
|
+
return _readRecord(value) ?? undefined
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function readOpenRouterUsageCost(usage: LanguageModelUsage): { cost?: number; upstreamInferenceCost?: number } {
|
|
@@ -45,21 +45,27 @@ export function readApprovalContinuationResponse(message: ChatMessage): Approval
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function isApprovalContinuationRequest(messages: ChatMessage[]): boolean {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
let lastAssistantIndex = -1
|
|
49
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
50
|
+
if (messages[i].role === 'assistant') {
|
|
51
|
+
lastAssistantIndex = i
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (lastAssistantIndex < 0) return false
|
|
50
56
|
|
|
51
|
-
const
|
|
52
|
-
const hasUserAfter = messages.slice(lastMessageIndex + 1).some((message) => message.role === 'user')
|
|
57
|
+
const hasUserAfter = messages.slice(lastAssistantIndex + 1).some((message) => message.role === 'user')
|
|
53
58
|
if (hasUserAfter) return false
|
|
54
59
|
|
|
55
|
-
return hasApprovalRespondedParts(
|
|
60
|
+
return hasApprovalRespondedParts(messages[lastAssistantIndex])
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
const PLAN_TOOL_NAMES = new Set([
|
|
59
64
|
'createExecutionPlan',
|
|
60
65
|
'replaceExecutionPlan',
|
|
61
66
|
'submitExecutionNodeResult',
|
|
62
|
-
'
|
|
67
|
+
'listExecutionPlans',
|
|
68
|
+
'getExecutionPlanDetails',
|
|
63
69
|
'resumeExecutionPlanRun',
|
|
64
70
|
])
|
|
65
71
|
|
|
@@ -7,5 +7,5 @@ export const CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES = ['userQuestions', 'proceed
|
|
|
7
7
|
export const CONTEXT_COMPACTION_INCLUDED_TOOL_PREFIXES = ['linear'] as const
|
|
8
8
|
export const MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES = 15
|
|
9
9
|
export const MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES = 10
|
|
10
|
-
export const
|
|
10
|
+
export const CONTEXT_WINDOW_TOKENS = 200_000
|
|
11
11
|
export const WORKSTREAM_RAW_TAIL_MESSAGES = 6
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createContextCompactionAgent } from '../system-agents/context-compaction.agent'
|
|
2
2
|
import {
|
|
3
3
|
buildContextCompactionPrompt,
|
|
4
4
|
buildMemoryBlockCompactionPrompt,
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import type { GenerateHelperStructuredParams, GenerateHelperTextParams } from './helper-model'
|
|
19
19
|
import { StructuredCompactionOutputSchema } from './workstream-state'
|
|
20
20
|
|
|
21
|
+
const CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS = 512
|
|
22
|
+
|
|
21
23
|
interface HelperModelRuntime {
|
|
22
24
|
generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T>
|
|
23
25
|
generateHelperText(params: GenerateHelperTextParams): Promise<string>
|
|
@@ -32,7 +34,7 @@ interface CreateContextCompactionRuntimeDeps {
|
|
|
32
34
|
async function runContextCompacter(helperModelRuntime: HelperModelRuntime, params: ContextCompactionRunnerParams) {
|
|
33
35
|
const output = await helperModelRuntime.generateHelperStructured({
|
|
34
36
|
tag: 'context-compaction',
|
|
35
|
-
createAgent:
|
|
37
|
+
createAgent: createContextCompactionAgent,
|
|
36
38
|
messages: [
|
|
37
39
|
{
|
|
38
40
|
role: 'user',
|
|
@@ -74,11 +76,11 @@ export function createWiredContextCompactionRuntime(deps: CreateContextCompactio
|
|
|
74
76
|
const newEntriesText = params.newEntriesText.trim()
|
|
75
77
|
if (!previousSummary && !newEntriesText) return ''
|
|
76
78
|
|
|
77
|
-
return
|
|
79
|
+
return helperModelRuntime.generateHelperText({
|
|
78
80
|
tag: 'memory-block-compaction',
|
|
79
|
-
createAgent:
|
|
81
|
+
createAgent: createContextCompactionAgent,
|
|
80
82
|
messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
|
|
81
|
-
maxOutputTokens:
|
|
83
|
+
maxOutputTokens: CONTEXT_COMPACTION_MAX_OUTPUT_TOKENS,
|
|
82
84
|
})
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto'
|
|
|
2
2
|
|
|
3
3
|
import type { ChatMessage } from '@lota-sdk/shared'
|
|
4
4
|
|
|
5
|
-
import { readString } from '../utils/string'
|
|
5
|
+
import { CHARS_PER_TOKEN_ESTIMATE, compactWhitespace, readRecord, readString, stringifyUnknown } from '../utils/string'
|
|
6
6
|
import {
|
|
7
7
|
COMPACTION_CHUNK_MAX_CHARS,
|
|
8
8
|
CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
|
|
@@ -15,14 +15,8 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
StructuredCompactionOutputSchema,
|
|
17
17
|
WorkstreamStateDeltaSchema,
|
|
18
|
-
WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS,
|
|
19
|
-
WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS,
|
|
20
|
-
WORKSTREAM_STATE_MAX_ARTIFACTS,
|
|
21
|
-
WORKSTREAM_STATE_MAX_KEY_DECISIONS,
|
|
22
|
-
WORKSTREAM_STATE_MAX_OPEN_QUESTIONS,
|
|
23
|
-
WORKSTREAM_STATE_MAX_RISKS,
|
|
24
|
-
WORKSTREAM_STATE_MAX_TASKS,
|
|
25
18
|
WorkstreamStateSchema,
|
|
19
|
+
applyWorkstreamStateCaps,
|
|
26
20
|
createEmptyWorkstreamState,
|
|
27
21
|
parseStructuredWorkstreamStateDelta,
|
|
28
22
|
} from './workstream-state'
|
|
@@ -99,15 +93,11 @@ const PROMPT_INJECTION_PATTERN =
|
|
|
99
93
|
/\b(ignore (all )?(previous|prior|system|developer)? instructions?|system prompt|developer prompt|tool override|jailbreak|role ?override|do not follow|bypass)\b/i
|
|
100
94
|
|
|
101
95
|
function estimateTokens(text: string): number {
|
|
102
|
-
return Math.ceil(text.length /
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function normalizeWhitespace(value: string): string {
|
|
106
|
-
return value.replace(/\s+/g, ' ').trim()
|
|
96
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE)
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
function sanitizeStateText(value: string): string | null {
|
|
110
|
-
const normalized =
|
|
100
|
+
const normalized = compactWhitespace(value)
|
|
111
101
|
if (!normalized) return null
|
|
112
102
|
if (PROMPT_INJECTION_PATTERN.test(normalized)) return null
|
|
113
103
|
return normalized
|
|
@@ -116,36 +106,18 @@ function sanitizeStateText(value: string): string | null {
|
|
|
116
106
|
function createStableId(prefix: string, ...parts: Array<string | number | undefined>): string {
|
|
117
107
|
const payload = parts
|
|
118
108
|
.map((part) => (part === undefined ? '' : String(part)))
|
|
119
|
-
.map((part) =>
|
|
109
|
+
.map((part) => compactWhitespace(part))
|
|
120
110
|
.join('|')
|
|
121
111
|
const hash = createHash('sha1').update(`${prefix}|${payload}`).digest('hex').slice(0, 20)
|
|
122
112
|
return `${prefix}_${hash}`
|
|
123
113
|
}
|
|
124
114
|
|
|
125
|
-
function readRecord(value: unknown): Record<string, unknown> | null {
|
|
126
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : null
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function stringifyUnknown(value: unknown): string | null {
|
|
130
|
-
if (value === undefined) return null
|
|
131
|
-
if (typeof value === 'string') {
|
|
132
|
-
const normalized = value.trim()
|
|
133
|
-
return normalized.length > 0 ? normalized : null
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
return JSON.stringify(value)
|
|
138
|
-
} catch {
|
|
139
|
-
return null
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
115
|
function appendUnique(values: string[], nextValues: string[]): string[] {
|
|
144
|
-
const seen = new Set(values.map((value) =>
|
|
116
|
+
const seen = new Set(values.map((value) => compactWhitespace(value).toLowerCase()))
|
|
145
117
|
const merged = [...values]
|
|
146
118
|
|
|
147
119
|
for (const value of nextValues) {
|
|
148
|
-
const normalized =
|
|
120
|
+
const normalized = compactWhitespace(value)
|
|
149
121
|
if (!normalized) continue
|
|
150
122
|
const key = normalized.toLowerCase()
|
|
151
123
|
if (seen.has(key)) continue
|
|
@@ -395,7 +367,7 @@ export function mergeStateDelta(
|
|
|
395
367
|
const normalizedRationale = sanitizeStateText(decision.rationale)
|
|
396
368
|
if (!normalizedDecision || !normalizedRationale) continue
|
|
397
369
|
|
|
398
|
-
const sourceIds = [...new Set(decision.sourceMessageIds.map((id) =>
|
|
370
|
+
const sourceIds = [...new Set(decision.sourceMessageIds.map((id) => compactWhitespace(id)).filter(Boolean))]
|
|
399
371
|
const decisionId = createStableId('decision', normalizedDecision, normalizedRationale, sourceIds.sort().join('|'))
|
|
400
372
|
const alreadyExists = state.keyDecisions.some((item) => item.id === decisionId)
|
|
401
373
|
if (alreadyExists) continue
|
|
@@ -403,7 +375,7 @@ export function mergeStateDelta(
|
|
|
403
375
|
id: decisionId,
|
|
404
376
|
decision: normalizedDecision,
|
|
405
377
|
rationale: normalizedRationale,
|
|
406
|
-
agent:
|
|
378
|
+
agent: compactWhitespace(decision.agent),
|
|
407
379
|
sourceMessageIds: sourceIds,
|
|
408
380
|
confidence: decision.confidence,
|
|
409
381
|
timestamp,
|
|
@@ -417,11 +389,9 @@ export function mergeStateDelta(
|
|
|
417
389
|
if (!title) continue
|
|
418
390
|
|
|
419
391
|
const externalId = sanitizeStateText(update.externalId ?? '')
|
|
420
|
-
const owner =
|
|
392
|
+
const owner = compactWhitespace(update.owner)
|
|
421
393
|
const taskId = externalId ? createStableId('task-external', externalId) : createStableId('task', title, owner)
|
|
422
|
-
const sourceMessageIds = [
|
|
423
|
-
...new Set(update.sourceMessageIds.map((id) => normalizeWhitespace(id)).filter(Boolean)),
|
|
424
|
-
]
|
|
394
|
+
const sourceMessageIds = [...new Set(update.sourceMessageIds.map((id) => compactWhitespace(id)).filter(Boolean))]
|
|
425
395
|
const existingIndex = state.tasks.findIndex((task) => task.id === taskId)
|
|
426
396
|
const nextTask = {
|
|
427
397
|
id: taskId,
|
|
@@ -455,7 +425,7 @@ export function mergeStateDelta(
|
|
|
455
425
|
const artifactId = createStableId('artifact', name, pointer)
|
|
456
426
|
const exists = state.artifacts.some((item) => item.id === artifactId)
|
|
457
427
|
if (exists) continue
|
|
458
|
-
state.artifacts.push({ id: artifactId, name, type:
|
|
428
|
+
state.artifacts.push({ id: artifactId, name, type: compactWhitespace(artifact.type), pointer, timestamp })
|
|
459
429
|
}
|
|
460
430
|
}
|
|
461
431
|
|
|
@@ -495,13 +465,7 @@ export function mergeStateDelta(
|
|
|
495
465
|
state.approvalNote = sanitizeStateText(delta.approvalNote) ?? undefined
|
|
496
466
|
}
|
|
497
467
|
|
|
498
|
-
state
|
|
499
|
-
state.activeConstraints = state.activeConstraints.slice(-WORKSTREAM_STATE_MAX_ACTIVE_CONSTRAINTS)
|
|
500
|
-
state.tasks = state.tasks.slice(-WORKSTREAM_STATE_MAX_TASKS)
|
|
501
|
-
state.openQuestions = state.openQuestions.slice(-WORKSTREAM_STATE_MAX_OPEN_QUESTIONS)
|
|
502
|
-
state.risks = state.risks.slice(-WORKSTREAM_STATE_MAX_RISKS)
|
|
503
|
-
state.artifacts = state.artifacts.slice(-WORKSTREAM_STATE_MAX_ARTIFACTS)
|
|
504
|
-
state.agentContributions = state.agentContributions.slice(-WORKSTREAM_STATE_MAX_AGENT_CONTRIBUTIONS)
|
|
468
|
+
applyWorkstreamStateCaps(state)
|
|
505
469
|
|
|
506
470
|
return WorkstreamStateSchema.parse(state)
|
|
507
471
|
}
|
|
@@ -727,9 +691,12 @@ export function createContextCompactionRuntime(
|
|
|
727
691
|
return summaryMessage ? [summaryMessage, ...liveMessages] : [...liveMessages]
|
|
728
692
|
}
|
|
729
693
|
|
|
694
|
+
const CONTEXT_OUTPUT_RESERVE_MAX_RATIO = 0.35
|
|
695
|
+
const CONTEXT_SAFETY_MARGIN_MAX_RATIO = 0.1
|
|
696
|
+
|
|
730
697
|
const estimateThreshold = (contextSize = 256_000): number => {
|
|
731
|
-
const reservedOutput = Math.min(outputReserveTokens, Math.floor(contextSize *
|
|
732
|
-
const safetyMargin = Math.min(safetyMarginTokens, Math.floor(contextSize *
|
|
698
|
+
const reservedOutput = Math.min(outputReserveTokens, Math.floor(contextSize * CONTEXT_OUTPUT_RESERVE_MAX_RATIO))
|
|
699
|
+
const safetyMargin = Math.min(safetyMarginTokens, Math.floor(contextSize * CONTEXT_SAFETY_MARGIN_MAX_RATIO))
|
|
733
700
|
const reservedThreshold = contextSize - (reservedOutput + safetyMargin)
|
|
734
701
|
const ratioThreshold = Math.floor(contextSize * thresholdRatio)
|
|
735
702
|
return Math.max(1_000, Math.min(contextSize - 1, Math.min(reservedThreshold, ratioThreshold)))
|
|
@@ -785,6 +752,24 @@ export function createContextCompactionRuntime(
|
|
|
785
752
|
const initialPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
|
|
786
753
|
const inputChars = initialPayload.length
|
|
787
754
|
|
|
755
|
+
const buildEarlyExitResult = (estimatedTokens: number): CompactHistoryResult => {
|
|
756
|
+
const exitSummaryPayload = buildSyntheticSummaryPayload(summaryText)
|
|
757
|
+
const outputPayload = JSON.stringify([...(exitSummaryPayload ? [exitSummaryPayload] : []), ...remainingMessages])
|
|
758
|
+
return {
|
|
759
|
+
compacted: compactedMessages.length > 0,
|
|
760
|
+
summaryText,
|
|
761
|
+
...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
|
|
762
|
+
compactedMessages,
|
|
763
|
+
compactedMessageCount: compactedMessages.length,
|
|
764
|
+
remainingMessageCount: remainingMessages.length,
|
|
765
|
+
estimatedTokens,
|
|
766
|
+
inputChars,
|
|
767
|
+
outputChars: outputPayload.length,
|
|
768
|
+
state,
|
|
769
|
+
stateDelta: mergedDelta,
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
788
773
|
for (;;) {
|
|
789
774
|
const assessment = shouldCompactHistory({
|
|
790
775
|
summaryText,
|
|
@@ -793,40 +778,12 @@ export function createContextCompactionRuntime(
|
|
|
793
778
|
})
|
|
794
779
|
|
|
795
780
|
if (!assessment.shouldCompact) {
|
|
796
|
-
|
|
797
|
-
const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
|
|
798
|
-
return {
|
|
799
|
-
compacted: compactedMessages.length > 0,
|
|
800
|
-
summaryText,
|
|
801
|
-
...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
|
|
802
|
-
compactedMessages,
|
|
803
|
-
compactedMessageCount: compactedMessages.length,
|
|
804
|
-
remainingMessageCount: remainingMessages.length,
|
|
805
|
-
estimatedTokens: assessment.estimatedTokens,
|
|
806
|
-
inputChars,
|
|
807
|
-
outputChars: outputPayload.length,
|
|
808
|
-
state,
|
|
809
|
-
stateDelta: mergedDelta,
|
|
810
|
-
}
|
|
781
|
+
return buildEarlyExitResult(assessment.estimatedTokens)
|
|
811
782
|
}
|
|
812
783
|
|
|
813
784
|
const boundary = Math.max(0, remainingMessages.length - params.tailMessageCount)
|
|
814
785
|
if (boundary <= 0) {
|
|
815
|
-
|
|
816
|
-
const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
|
|
817
|
-
return {
|
|
818
|
-
compacted: compactedMessages.length > 0,
|
|
819
|
-
summaryText,
|
|
820
|
-
...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
|
|
821
|
-
compactedMessages,
|
|
822
|
-
compactedMessageCount: compactedMessages.length,
|
|
823
|
-
remainingMessageCount: remainingMessages.length,
|
|
824
|
-
estimatedTokens: assessment.estimatedTokens,
|
|
825
|
-
inputChars,
|
|
826
|
-
outputChars: outputPayload.length,
|
|
827
|
-
state,
|
|
828
|
-
stateDelta: mergedDelta,
|
|
829
|
-
}
|
|
786
|
+
return buildEarlyExitResult(assessment.estimatedTokens)
|
|
830
787
|
}
|
|
831
788
|
|
|
832
789
|
const candidatePrefix = remainingMessages.slice(0, boundary)
|
|
@@ -834,22 +791,8 @@ export function createContextCompactionRuntime(
|
|
|
834
791
|
const contextMessages = messagesToCompact.map(toContextMessageFromChatMessage)
|
|
835
792
|
const sourceText = toCompactionTranscript(contextMessages)
|
|
836
793
|
|
|
837
|
-
if (!
|
|
838
|
-
|
|
839
|
-
const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
|
|
840
|
-
return {
|
|
841
|
-
compacted: compactedMessages.length > 0,
|
|
842
|
-
summaryText,
|
|
843
|
-
...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
|
|
844
|
-
compactedMessages,
|
|
845
|
-
compactedMessageCount: compactedMessages.length,
|
|
846
|
-
remainingMessageCount: remainingMessages.length,
|
|
847
|
-
estimatedTokens: assessment.estimatedTokens,
|
|
848
|
-
inputChars,
|
|
849
|
-
outputChars: outputPayload.length,
|
|
850
|
-
state,
|
|
851
|
-
stateDelta: mergedDelta,
|
|
852
|
-
}
|
|
794
|
+
if (!compactWhitespace(sourceText)) {
|
|
795
|
+
return buildEarlyExitResult(assessment.estimatedTokens)
|
|
853
796
|
}
|
|
854
797
|
|
|
855
798
|
let compactionOutput = await compactContextMessages({
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { SerializableExecutionPlan } from '@lota-sdk/shared'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
|
|
4
4
|
- Before doing multi-step work, create a contract-driven execution plan instead of tracking steps only in prose.
|
|
5
|
+
- A workstream may have multiple active execution plans. Review all plans before creating new ones.
|
|
5
6
|
- Plans are graph-capable workflow contracts. Every execution node must define objective, instructions, deliverables, success criteria, completion checks, retry policy, failure policy, and tool/context policy.
|
|
6
7
|
- The runtime executor owns lifecycle truth. Do not claim that a node is complete until submitExecutionNodeResult succeeds.
|
|
7
|
-
- Use execution-plan tools to create, replace, inspect,
|
|
8
|
-
-
|
|
8
|
+
- Use execution-plan tools to create, replace, inspect, and resume runs.
|
|
9
|
+
- Visible workstream agents do not manually submit node results; dispatched execution nodes are completed by the runtime executor.
|
|
10
|
+
- Treat the active execution runs in <execution-plan-state> as authoritative. Do not mutate run or node status in prose.
|
|
9
11
|
- Work only on nodes that are active or explicitly ready for your executor. If a node is awaiting human input or approval, stop and let the runtime resume it.
|
|
10
12
|
- If the graph, contracts, or success criteria materially change, replace the plan instead of silently drifting.
|
|
11
13
|
</execution-plan-protocol>`
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
+
function formatExecutionPlansForPrompt(plans: SerializableExecutionPlan[]): string | undefined {
|
|
16
|
+
if (plans.length === 0) return undefined
|
|
15
17
|
|
|
16
18
|
const payload = {
|
|
17
19
|
policy: {
|
|
@@ -21,46 +23,48 @@ export function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | n
|
|
|
21
23
|
artifactsAreFirstClassOutputs: true,
|
|
22
24
|
checkpointRecoveryEnabled: true,
|
|
23
25
|
},
|
|
24
|
-
|
|
26
|
+
activePlans: plans,
|
|
27
|
+
planCount: plans.length,
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
return ['<execution-plan-state>', JSON.stringify(payload, null, 2), '</execution-plan-state>'].join('\n')
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function buildExecutionPlanInstructionSections(
|
|
31
|
-
|
|
34
|
+
plans: SerializableExecutionPlan[] | null | undefined,
|
|
32
35
|
): string[] | undefined {
|
|
36
|
+
const normalized = plans ?? []
|
|
33
37
|
const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
sections.push(
|
|
38
|
+
const stateSection = formatExecutionPlansForPrompt(normalized)
|
|
39
|
+
if (stateSection) {
|
|
40
|
+
sections.push(stateSection)
|
|
37
41
|
}
|
|
38
42
|
return sections
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export function createExecutionPlanInstructionSectionCache(params: {
|
|
42
46
|
disabled?: boolean
|
|
43
|
-
|
|
47
|
+
loadPlans: () => Promise<SerializableExecutionPlan[]>
|
|
44
48
|
}) {
|
|
45
|
-
let
|
|
49
|
+
let plansPromise: Promise<SerializableExecutionPlan[]> | null = null
|
|
46
50
|
let sectionsPromise: Promise<string[] | undefined> | null = null
|
|
47
51
|
|
|
48
52
|
return {
|
|
49
53
|
invalidate() {
|
|
50
|
-
|
|
54
|
+
plansPromise = null
|
|
51
55
|
sectionsPromise = null
|
|
52
56
|
},
|
|
53
|
-
async
|
|
54
|
-
if (params.disabled) return
|
|
57
|
+
async getPlans(): Promise<SerializableExecutionPlan[]> {
|
|
58
|
+
if (params.disabled) return []
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
return
|
|
60
|
+
plansPromise ??= params.loadPlans()
|
|
61
|
+
return plansPromise
|
|
58
62
|
},
|
|
59
63
|
async getSections(): Promise<string[] | undefined> {
|
|
60
64
|
if (params.disabled) return undefined
|
|
61
65
|
|
|
62
|
-
sectionsPromise ??= this.
|
|
63
|
-
return
|
|
66
|
+
sectionsPromise ??= this.getPlans().then((plans) => buildExecutionPlanInstructionSections(plans))
|
|
67
|
+
return sectionsPromise
|
|
64
68
|
},
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GraphDesignRequest, GraphDesignResponse } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
export interface GraphDesigner {
|
|
4
|
+
designGraph(request: GraphDesignRequest): Promise<GraphDesignResponse>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let _graphDesigner: GraphDesigner | null = null
|
|
8
|
+
|
|
9
|
+
export function configureGraphDesigner(designer: GraphDesigner): void {
|
|
10
|
+
_graphDesigner = designer
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getGraphDesigner(): GraphDesigner | null {
|
|
14
|
+
return _graphDesigner
|
|
15
|
+
}
|