@lota-sdk/core 0.1.15 → 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 +8 -7
- package/src/ai/definitions.ts +80 -2
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/config/agent-defaults.ts +31 -21
- 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/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +244 -178
- 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 +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- 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/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -56
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +22 -60
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +2 -0
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +2 -2
- 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 +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- 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 +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- 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 +1 -1
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -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 +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +42 -59
- 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 +11 -4
- 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-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 +384 -40
- 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 +84 -2
- 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.service.ts +27 -31
- 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 +12 -34
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -66
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/delegated-agent-factory.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/team-think.tool.ts +6 -4
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +2 -2
- package/src/workers/skill-extraction.runner.ts +1 -1
- package/src/workers/utils/file-section-chunker.ts +2 -1
- 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 +11 -20
- package/src/workers/worker-utils.ts +2 -2
- package/src/tools/log-hello-world.tool.ts +0 -17
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PlanRunRecord, SerializableExecutionPlan } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import type { RecordIdInput } from '../db/record-id'
|
|
4
|
+
import { planRunService } from './plan-run.service'
|
|
5
|
+
|
|
6
|
+
class WorkstreamPlanRegistryService {
|
|
7
|
+
async listActiveRuns(workstreamId: RecordIdInput): Promise<PlanRunRecord[]> {
|
|
8
|
+
return planRunService.getActiveRunRecords(workstreamId)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async countActiveRuns(workstreamId: RecordIdInput): Promise<number> {
|
|
12
|
+
const runs = await this.listActiveRuns(workstreamId)
|
|
13
|
+
return runs.length
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async listActivePlans(workstreamId: RecordIdInput): Promise<SerializableExecutionPlan[]> {
|
|
17
|
+
const runs = await this.listActiveRuns(workstreamId)
|
|
18
|
+
return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run)))
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const workstreamPlanRegistryService = new WorkstreamPlanRegistryService()
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from '../system-agents/title-generator.agent'
|
|
11
11
|
import { workstreamService } from './workstream.service'
|
|
12
12
|
|
|
13
|
+
const WORKSTREAM_TITLE_TIMEOUT_MS = 5_000
|
|
14
|
+
|
|
13
15
|
class WorkstreamTitleService {
|
|
14
16
|
helperRuntime = createHelperModelRuntime()
|
|
15
17
|
|
|
@@ -21,7 +23,7 @@ class WorkstreamTitleService {
|
|
|
21
23
|
tag: 'workstream-title',
|
|
22
24
|
createAgent: createWorkstreamTitleGeneratorAgent,
|
|
23
25
|
defaultSystemPrompt: WORKSTREAM_TITLE_GENERATOR_PROMPT,
|
|
24
|
-
timeoutMs:
|
|
26
|
+
timeoutMs: WORKSTREAM_TITLE_TIMEOUT_MS,
|
|
25
27
|
messages: [{ role: 'user', content: sourceText }],
|
|
26
28
|
}),
|
|
27
29
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
WORKSTREAM,
|
|
2
3
|
baseChatMessageSchema,
|
|
3
4
|
CONSULT_SPECIALIST_TOOL_NAME,
|
|
4
5
|
CONSULT_TEAM_TOOL_NAME,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
} from '@lota-sdk/shared'
|
|
11
12
|
import type { ChatMessage, MessageMetadata } from '@lota-sdk/shared'
|
|
12
13
|
import { convertToModelMessages, readUIMessageStream, stepCountIs, tool as createTool, validateUIMessages } from 'ai'
|
|
13
|
-
import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
|
|
14
|
+
import type { PrepareStepFunction, StopCondition, ToolLoopAgent, ToolSet, UIMessageStreamWriter } from 'ai'
|
|
14
15
|
import type { z } from 'zod'
|
|
15
16
|
|
|
16
17
|
import type { CoreWorkstreamProfile } from '../config/agent-defaults'
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
getLeadAgentId,
|
|
22
23
|
getCoreWorkstreamProfile,
|
|
23
24
|
getAgentRuntimeConfig,
|
|
24
|
-
pluginRuntime,
|
|
25
25
|
} from '../config/agent-defaults'
|
|
26
26
|
import { lotaDebugLogger } from '../config/debug-logger'
|
|
27
27
|
import { aiLogger } from '../config/logger'
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
shouldEnqueueOnboardingPostChatMemory,
|
|
56
56
|
shouldEnqueueRegularDigestForWorkstream,
|
|
57
57
|
} from '../runtime/memory-digest-policy'
|
|
58
|
+
import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
|
|
58
59
|
import { getRuntimeAdapters, getToolProviders, getTurnHooks } from '../runtime/runtime-extensions'
|
|
59
60
|
import { shouldEnqueueSkillExtraction } from '../runtime/skill-extraction-policy'
|
|
60
61
|
import { finalizeTurnRun } from '../runtime/turn-lifecycle'
|
|
@@ -86,12 +87,11 @@ import { contextCompactionRuntime } from './context-compaction-runtime.singleton
|
|
|
86
87
|
import { executionPlanService } from './execution-plan.service'
|
|
87
88
|
import { learnedSkillService } from './learned-skill.service'
|
|
88
89
|
import { memoryService } from './memory.service'
|
|
90
|
+
import { planRunService } from './plan-run.service'
|
|
89
91
|
import { recentActivityService } from './recent-activity.service'
|
|
90
92
|
import { workstreamMessageService } from './workstream-message.service'
|
|
91
93
|
import { workstreamService } from './workstream.service'
|
|
92
94
|
|
|
93
|
-
type AgentRuntimeConfig = Record<string, unknown>
|
|
94
|
-
type AgentFactory = Record<string, (...args: unknown[]) => Record<string, (...args: unknown[]) => unknown>>
|
|
95
95
|
type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
|
|
96
96
|
|
|
97
97
|
interface UIMessageStreamResult {
|
|
@@ -106,45 +106,8 @@ function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
|
|
|
106
106
|
)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function getPluginService(path: string[]): ((...args: unknown[]) => unknown) | undefined {
|
|
110
|
-
let current: unknown = pluginRuntime
|
|
111
|
-
let owner: unknown = undefined
|
|
112
|
-
for (const key of path) {
|
|
113
|
-
if (current === null || current === undefined || typeof current !== 'object') return undefined
|
|
114
|
-
owner = current
|
|
115
|
-
current = (current as Record<string, unknown>)[key]
|
|
116
|
-
}
|
|
117
|
-
if (typeof current !== 'function') {
|
|
118
|
-
return undefined
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return owner && typeof owner === 'object'
|
|
122
|
-
? (current as (...args: unknown[]) => unknown).bind(owner)
|
|
123
|
-
: (current as (...args: unknown[]) => unknown)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function buildIndexedRepositoriesContext(
|
|
127
|
-
workspaceId: string,
|
|
128
|
-
): Promise<{ provideRepoTool: boolean; defaultSectionsByAgent: Record<string, unknown>; context: string }> {
|
|
129
|
-
const buildContext = getRuntimeAdapters().workstream?.buildIndexedRepositoriesContext
|
|
130
|
-
if (!buildContext) {
|
|
131
|
-
return { provideRepoTool: false, defaultSectionsByAgent: {}, context: '' }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const context = await buildContext(workspaceId)
|
|
135
|
-
return {
|
|
136
|
-
provideRepoTool: context.provideRepoTool,
|
|
137
|
-
defaultSectionsByAgent: context.defaultSectionsByAgent,
|
|
138
|
-
context: context.context ?? '',
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
109
|
const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
|
|
143
110
|
|
|
144
|
-
function parsePersistedWorkstreamState(value: unknown): WorkstreamState | null {
|
|
145
|
-
return parseWorkstreamState(value)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
111
|
function stripExecutionPlanFieldsFromWorkstreamState(
|
|
149
112
|
state: WorkstreamState | null | undefined,
|
|
150
113
|
hasExecutionPlan: boolean,
|
|
@@ -155,7 +118,7 @@ function stripExecutionPlanFieldsFromWorkstreamState(
|
|
|
155
118
|
}
|
|
156
119
|
|
|
157
120
|
async function waitForWorkstreamCompactionIfNeeded(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
|
|
158
|
-
return
|
|
121
|
+
return waitForCompactionIfNeeded({
|
|
159
122
|
entityId: recordIdToString(workstreamId, TABLES.WORKSTREAM),
|
|
160
123
|
entityLabel: 'Workstream',
|
|
161
124
|
loadEntity: () => workstreamService.getById(workstreamId),
|
|
@@ -397,18 +360,18 @@ async function streamAgentResponse(
|
|
|
397
360
|
optionalInstructionSection(agentResolution?.extraInstructions),
|
|
398
361
|
),
|
|
399
362
|
context: ctx.buildContextResult,
|
|
400
|
-
}) as
|
|
363
|
+
}) as Record<string, unknown>
|
|
401
364
|
agentTimer.step('build-agent-config')
|
|
402
365
|
const modelMessages = await convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true })
|
|
403
366
|
agentTimer.step('convert-model-messages')
|
|
404
|
-
const agent =
|
|
367
|
+
const agent = createAgent[config.id as string]({
|
|
405
368
|
mode: streamParams.mode,
|
|
406
369
|
tools: streamParams.tools,
|
|
407
370
|
extraInstructions: config.extraInstructions,
|
|
408
371
|
stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
|
|
409
372
|
streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
|
|
410
373
|
prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
|
|
411
|
-
})
|
|
374
|
+
}) as ToolLoopAgent<never, ToolSet>
|
|
412
375
|
const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
|
|
413
376
|
agentTimer.step('agent-construction')
|
|
414
377
|
|
|
@@ -443,7 +406,7 @@ async function streamAgentResponse(
|
|
|
443
406
|
sendSources: true,
|
|
444
407
|
messageMetadata: createAgentMessageMetadata({ agentId: resolvedAgentId, agentName: config.displayName as string }),
|
|
445
408
|
onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
|
|
446
|
-
responseMessage = withMessageCreatedAt(finishedResponseMessage)
|
|
409
|
+
responseMessage = withMessageCreatedAt(finishedResponseMessage, Date.now())
|
|
447
410
|
resolveFinishedStream()
|
|
448
411
|
},
|
|
449
412
|
}) as ReadableStream<ChatStreamChunk>
|
|
@@ -474,7 +437,7 @@ async function streamAgentResponse(
|
|
|
474
437
|
}
|
|
475
438
|
|
|
476
439
|
for (const toolError of collectToolOutputErrors({ responseMessage: responseMessage })) {
|
|
477
|
-
aiLogger.
|
|
440
|
+
aiLogger.error`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
|
|
478
441
|
}
|
|
479
442
|
|
|
480
443
|
return responseMessage
|
|
@@ -657,7 +620,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
657
620
|
const shouldProcessPostRunSideEffects =
|
|
658
621
|
params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage
|
|
659
622
|
if (params.kind === 'userTurn') {
|
|
660
|
-
inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage))
|
|
623
|
+
inputMessage = hydrateMessageFileUrls(withMessageCreatedAt(params.inputMessage, Date.now()))
|
|
661
624
|
if (inputMessage.role !== 'user') {
|
|
662
625
|
throw new WorkstreamTurnError('Only user messages can be submitted to the workstream runtime.', 400)
|
|
663
626
|
}
|
|
@@ -691,7 +654,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
691
654
|
timer.step('persist-approval-message')
|
|
692
655
|
}
|
|
693
656
|
|
|
694
|
-
const initialWorkstreamState =
|
|
657
|
+
const initialWorkstreamState = parseWorkstreamState(workstreamRecord.state)
|
|
695
658
|
const persistedCompactionCursor = toOptionalTrimmedString(workstreamRecord.lastCompactedMessageId) ?? undefined
|
|
696
659
|
const persistedLiveHistoryPromise = workstreamMessageService.listMessagesAfterCursor(
|
|
697
660
|
workstreamRef,
|
|
@@ -703,10 +666,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
703
666
|
if (inputMessage) {
|
|
704
667
|
userMessage = {
|
|
705
668
|
...inputMessage,
|
|
706
|
-
id: inputMessage.id
|
|
669
|
+
id: inputMessage.id,
|
|
707
670
|
role: 'user',
|
|
708
671
|
parts: inputMessage.parts,
|
|
709
|
-
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt)
|
|
672
|
+
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) },
|
|
710
673
|
}
|
|
711
674
|
}
|
|
712
675
|
|
|
@@ -758,6 +721,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
758
721
|
workstream.mode === 'group' &&
|
|
759
722
|
!workstream.core &&
|
|
760
723
|
workstreamRecord.nameGenerated !== true &&
|
|
724
|
+
workstreamRecord.title === WORKSTREAM.DEFAULT_TITLE &&
|
|
761
725
|
messageText.length > 0
|
|
762
726
|
) {
|
|
763
727
|
void safeEnqueue(
|
|
@@ -896,18 +860,21 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
896
860
|
let workstreamState = initialWorkstreamState
|
|
897
861
|
const executionPlanInstructionSectionCache = createExecutionPlanInstructionSectionCache({
|
|
898
862
|
disabled: onboardingActive,
|
|
899
|
-
|
|
863
|
+
loadPlans: async () => {
|
|
864
|
+
const runs = await planRunService.getActiveRunRecords(workstreamRef)
|
|
865
|
+
return Promise.all(runs.map((run) => planRunService.toSerializablePlan(run)))
|
|
866
|
+
},
|
|
900
867
|
})
|
|
901
|
-
const
|
|
868
|
+
const getExecutionPlans = async () => await executionPlanInstructionSectionCache.getPlans()
|
|
902
869
|
const getExecutionPlanInstructionSections = async (): Promise<string[] | undefined> =>
|
|
903
870
|
await executionPlanInstructionSectionCache.getSections()
|
|
904
871
|
const invalidateExecutionPlanInstructionSections = () => {
|
|
905
872
|
executionPlanInstructionSectionCache.invalidate()
|
|
906
873
|
}
|
|
907
874
|
const getWorkstreamStateSection = async (): Promise<string | undefined> => {
|
|
908
|
-
const
|
|
875
|
+
const executionPlans = await getExecutionPlans()
|
|
909
876
|
return contextCompactionRuntime.formatWorkstreamStateForPrompt(
|
|
910
|
-
stripExecutionPlanFieldsFromWorkstreamState(workstreamState,
|
|
877
|
+
stripExecutionPlanFieldsFromWorkstreamState(workstreamState, executionPlans.length > 0),
|
|
911
878
|
)
|
|
912
879
|
}
|
|
913
880
|
const respondedBy = recordIdToString(userRef, TABLES.USER)
|
|
@@ -1018,10 +985,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1018
985
|
})
|
|
1019
986
|
|
|
1020
987
|
const commitAssistantResponse = async (response: ChatMessage, agentId: string, agentName: string) => {
|
|
1021
|
-
const committed = withMessageCreatedAt(
|
|
1022
|
-
...response,
|
|
1023
|
-
|
|
1024
|
-
|
|
988
|
+
const committed = withMessageCreatedAt(
|
|
989
|
+
{ ...response, metadata: { ...response.metadata, ...buildAgentMetadataPatch(agentId, agentName) } },
|
|
990
|
+
Date.now(),
|
|
991
|
+
)
|
|
1025
992
|
|
|
1026
993
|
await workstreamMessageService.upsertMessages({ workstreamId: workstreamRef, messages: [committed] })
|
|
1027
994
|
currentMessages = upsertChatHistoryMessage(currentMessages, committed)
|
|
@@ -1063,6 +1030,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1063
1030
|
}): Promise<ChatMessage> => {
|
|
1064
1031
|
const visibleTimer = lotaDebugLogger.timer(`visible:${runParams.agentId}`)
|
|
1065
1032
|
let runMemoryBlock = memoryBlock
|
|
1033
|
+
const includeExecutionPlanTools = runParams.mode !== 'fixedWorkstreamMode' && !onboardingActive
|
|
1066
1034
|
const tools: ToolSet = {
|
|
1067
1035
|
...((await buildAgentTools({
|
|
1068
1036
|
agentId: runParams.agentId,
|
|
@@ -1084,7 +1052,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1084
1052
|
runMemoryBlock = value
|
|
1085
1053
|
},
|
|
1086
1054
|
availableUploads: listReadableUploads(runParams.extraMessages),
|
|
1087
|
-
includeExecutionPlanTools
|
|
1055
|
+
includeExecutionPlanTools,
|
|
1088
1056
|
onExecutionPlanChanged: invalidateExecutionPlanInstructionSections,
|
|
1089
1057
|
context: buildContextResult,
|
|
1090
1058
|
})) as ToolSet),
|
|
@@ -1107,7 +1075,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1107
1075
|
visibleTimer.step('stream-agent-response')
|
|
1108
1076
|
memoryBlock = runMemoryBlock
|
|
1109
1077
|
|
|
1110
|
-
return
|
|
1078
|
+
return commitAssistantResponse(
|
|
1111
1079
|
responseMessage,
|
|
1112
1080
|
runParams.agentId,
|
|
1113
1081
|
agentDisplayNames[runParams.agentId] ?? runParams.agentId,
|
|
@@ -1182,14 +1150,14 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1182
1150
|
hookInstructionSections,
|
|
1183
1151
|
),
|
|
1184
1152
|
context: buildContextResult,
|
|
1185
|
-
}) as
|
|
1153
|
+
}) as Record<string, unknown>
|
|
1186
1154
|
const observer = createObserver(agentId)
|
|
1187
|
-
const agent =
|
|
1155
|
+
const agent = createAgent[specialistConfig.id as string]({
|
|
1188
1156
|
mode: 'fixedWorkstreamMode',
|
|
1189
1157
|
tools: { ...(specialistTools as ToolSet), ...toolProviders },
|
|
1190
1158
|
extraInstructions: specialistConfig.extraInstructions,
|
|
1191
1159
|
stopWhen: [stepCountIs(specialistConfig.maxSteps as number)],
|
|
1192
|
-
})
|
|
1160
|
+
}) as ToolLoopAgent<never, ToolSet>
|
|
1193
1161
|
const modelMessages = await convertToModelMessages(buildRunInputMessages([specialistTaskMessage]), {
|
|
1194
1162
|
ignoreIncompleteToolCalls: true,
|
|
1195
1163
|
})
|
|
@@ -1225,7 +1193,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1225
1193
|
}),
|
|
1226
1194
|
}) as ReadableStream<never>,
|
|
1227
1195
|
})) {
|
|
1228
|
-
finalMessage = withMessageCreatedAt(message)
|
|
1196
|
+
finalMessage = withMessageCreatedAt(message, Date.now())
|
|
1229
1197
|
yield finalMessage
|
|
1230
1198
|
}
|
|
1231
1199
|
|
|
@@ -1301,7 +1269,7 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1301
1269
|
} finally {
|
|
1302
1270
|
try {
|
|
1303
1271
|
const latestWorkstreamRecord = await workstreamService.getById(workstreamRef)
|
|
1304
|
-
const latestPersistedState =
|
|
1272
|
+
const latestPersistedState = parseWorkstreamState(latestWorkstreamRecord.state)
|
|
1305
1273
|
|
|
1306
1274
|
await finalizeTurnRun({
|
|
1307
1275
|
serverRunId,
|
|
@@ -99,7 +99,7 @@ function requireDirectAgentId(agentId: string | undefined): string {
|
|
|
99
99
|
return agentId
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function
|
|
102
|
+
function requireString(coreType: string | undefined): string {
|
|
103
103
|
if (!coreType) {
|
|
104
104
|
throw new Error('Core workstreams require a coreType')
|
|
105
105
|
}
|
|
@@ -174,7 +174,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
174
174
|
return options.title
|
|
175
175
|
}
|
|
176
176
|
if (core) {
|
|
177
|
-
return getCoreWorkstreamProfile(
|
|
177
|
+
return getCoreWorkstreamProfile(requireString(coreType)).config.title
|
|
178
178
|
}
|
|
179
179
|
if (mode === 'direct') {
|
|
180
180
|
return getAgentDisplayName(requireDirectAgentId(directAgentId))
|
|
@@ -220,7 +220,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
if (core) {
|
|
223
|
-
const resolvedCoreType =
|
|
223
|
+
const resolvedCoreType = requireString(coreType)
|
|
224
224
|
const coreProfile = getCoreWorkstreamProfile(resolvedCoreType)
|
|
225
225
|
const coreWorkstreamId = buildCoreWorkstreamId({ userId, orgId, coreType: resolvedCoreType })
|
|
226
226
|
const existing = await this.findById(coreWorkstreamId)
|
|
@@ -273,6 +273,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
273
273
|
core: false,
|
|
274
274
|
title,
|
|
275
275
|
status: 'regular',
|
|
276
|
+
nameGenerated: options?.title !== undefined && options.title !== WORKSTREAM.DEFAULT_TITLE,
|
|
276
277
|
})
|
|
277
278
|
|
|
278
279
|
return this.normalizeWorkstream(groupWorkstream)
|
|
@@ -294,7 +295,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
294
295
|
)
|
|
295
296
|
|
|
296
297
|
const hasStandardGroupWorkstream = existingWorkstreams.some(
|
|
297
|
-
(workstream) => workstream.mode === 'group' && workstream.core
|
|
298
|
+
(workstream) => workstream.mode === 'group' && !workstream.core,
|
|
298
299
|
)
|
|
299
300
|
const directWorkstreamsByAgent = new Map<string, WorkstreamRecord>()
|
|
300
301
|
const coreWorkstreamsByType = new Map<string, WorkstreamRecord>()
|
|
@@ -303,7 +304,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
303
304
|
directWorkstreamsByAgent.set(workstream.agentId, workstream)
|
|
304
305
|
}
|
|
305
306
|
for (const workstream of existingWorkstreams) {
|
|
306
|
-
if (workstream.mode !== 'group' || workstream.core
|
|
307
|
+
if (workstream.mode !== 'group' || !workstream.core) continue
|
|
307
308
|
if (typeof workstream.coreType !== 'string') continue
|
|
308
309
|
coreWorkstreamsByType.set(workstream.coreType, workstream)
|
|
309
310
|
}
|
|
@@ -457,14 +458,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
457
458
|
return this.normalizeWorkstream(workstream)
|
|
458
459
|
}
|
|
459
460
|
|
|
460
|
-
async getWorkstreamRecord(workstreamId: RecordIdRef): Promise<WorkstreamRecord> {
|
|
461
|
-
return await this.getById(workstreamId)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
461
|
async updateTitle(workstreamId: RecordIdRef, title: string): Promise<NormalizedWorkstream> {
|
|
465
462
|
const existing = await this.getById(workstreamId)
|
|
466
463
|
this.assertMutableWorkstream(existing, 'rename')
|
|
467
|
-
const workstream = await this.update(workstreamId, { title })
|
|
464
|
+
const workstream = await this.update(workstreamId, { title, nameGenerated: true })
|
|
468
465
|
return this.normalizeWorkstream(workstream)
|
|
469
466
|
}
|
|
470
467
|
|
|
@@ -501,9 +498,8 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
501
498
|
}
|
|
502
499
|
|
|
503
500
|
async clearActiveRunIdIfMatches(workstreamId: RecordIdRef, runId: string): Promise<void> {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
await this.setActiveRunId(workstreamId, null)
|
|
501
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
502
|
+
await databaseService.query(surql`UPDATE ONLY ${workstreamRef} SET activeRunId = NONE WHERE activeRunId = ${runId}`)
|
|
507
503
|
}
|
|
508
504
|
|
|
509
505
|
async setActiveStreamId(workstreamId: RecordIdRef, streamId: string): Promise<void> {
|
|
@@ -523,13 +519,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
523
519
|
}
|
|
524
520
|
|
|
525
521
|
async clearActiveStreamIdIfMatches(workstreamId: RecordIdRef, streamId: string): Promise<void> {
|
|
526
|
-
const activeStreamId = await this.getActiveStreamId(workstreamId)
|
|
527
|
-
if (activeStreamId !== streamId) return
|
|
528
522
|
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
529
|
-
await databaseService.query
|
|
530
|
-
UPDATE ONLY ${workstreamRef}
|
|
531
|
-
|
|
532
|
-
`)
|
|
523
|
+
await databaseService.query(
|
|
524
|
+
surql`UPDATE ONLY ${workstreamRef} SET activeStreamId = NONE WHERE activeStreamId = ${streamId}`,
|
|
525
|
+
)
|
|
533
526
|
}
|
|
534
527
|
|
|
535
528
|
async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
|
|
@@ -659,7 +652,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
659
652
|
throw new Error(`Invalid record id for table ${table}`)
|
|
660
653
|
}
|
|
661
654
|
|
|
662
|
-
return recordIdToString(id,
|
|
655
|
+
return recordIdToString(id, table)
|
|
663
656
|
}
|
|
664
657
|
|
|
665
658
|
formatMemoryBlockForPrompt(workstream: Pick<WorkstreamRecord, 'memoryBlock' | 'memoryBlockSummary'>): string {
|
|
@@ -670,7 +663,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
670
663
|
}
|
|
671
664
|
|
|
672
665
|
private getDefaultTitle(workstream: Pick<WorkstreamRecord, 'core' | 'coreType'>): string {
|
|
673
|
-
if (workstream.core
|
|
666
|
+
if (workstream.core && typeof workstream.coreType === 'string') {
|
|
674
667
|
return getCoreWorkstreamProfile(workstream.coreType).config.title
|
|
675
668
|
}
|
|
676
669
|
|
|
@@ -683,10 +676,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
683
676
|
? workstream.activeRunId
|
|
684
677
|
: null
|
|
685
678
|
const isCompacting = workstream.isCompacting === true
|
|
686
|
-
const mode =
|
|
687
|
-
const core = workstream.core
|
|
679
|
+
const mode = workstream.mode
|
|
680
|
+
const core = workstream.core
|
|
688
681
|
const coreType = core && typeof workstream.coreType === 'string' ? workstream.coreType : undefined
|
|
689
|
-
const status =
|
|
682
|
+
const status = workstream.status
|
|
690
683
|
return {
|
|
691
684
|
id: this.normalizeWorkstreamId(workstream.id),
|
|
692
685
|
userId: this.normalizeRecordIdString(workstream.userId, TABLES.USER),
|
|
@@ -694,7 +687,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
694
687
|
mode,
|
|
695
688
|
core,
|
|
696
689
|
...(coreType ? { coreType } : {}),
|
|
697
|
-
nameGenerated: workstream.nameGenerated
|
|
690
|
+
nameGenerated: workstream.nameGenerated,
|
|
698
691
|
isRunning: activeRunId !== null,
|
|
699
692
|
isCompacting,
|
|
700
693
|
...(isAgentName(workstream.agentId) ? { agentId: workstream.agentId } : {}),
|
|
@@ -706,35 +699,20 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
706
699
|
}
|
|
707
700
|
}
|
|
708
701
|
|
|
709
|
-
toPublicWorkstream(workstream: NormalizedWorkstream
|
|
710
|
-
const id = typeof workstream.id === 'string' ? workstream.id : this.normalizeWorkstreamId(workstream.id)
|
|
711
|
-
const createdAt = toIsoDateTimeString(workstream.createdAt)
|
|
712
|
-
const updatedAt = toIsoDateTimeString(workstream.updatedAt)
|
|
713
|
-
const activeRunId =
|
|
714
|
-
'activeRunId' in workstream &&
|
|
715
|
-
typeof workstream.activeRunId === 'string' &&
|
|
716
|
-
workstream.activeRunId.trim().length > 0
|
|
717
|
-
? workstream.activeRunId
|
|
718
|
-
: null
|
|
719
|
-
const isRunning = 'isRunning' in workstream ? workstream.isRunning : activeRunId !== null
|
|
720
|
-
const isCompacting = workstream.isCompacting === true
|
|
721
|
-
const mode = typeof workstream.mode === 'string' ? workstream.mode : 'group'
|
|
722
|
-
const core = workstream.core === true
|
|
723
|
-
const coreType = core && typeof workstream.coreType === 'string' ? workstream.coreType : undefined
|
|
724
|
-
const nameGenerated = 'nameGenerated' in workstream ? workstream.nameGenerated === true : false
|
|
702
|
+
toPublicWorkstream(workstream: NormalizedWorkstream) {
|
|
725
703
|
return {
|
|
726
|
-
id,
|
|
727
|
-
mode,
|
|
728
|
-
core,
|
|
729
|
-
...(coreType ? { coreType } : {}),
|
|
730
|
-
...(
|
|
731
|
-
title: workstream.title
|
|
732
|
-
status: workstream.status
|
|
733
|
-
nameGenerated,
|
|
734
|
-
isRunning,
|
|
735
|
-
isCompacting,
|
|
736
|
-
createdAt,
|
|
737
|
-
updatedAt,
|
|
704
|
+
id: workstream.id,
|
|
705
|
+
mode: workstream.mode,
|
|
706
|
+
core: workstream.core,
|
|
707
|
+
...(workstream.coreType ? { coreType: workstream.coreType } : {}),
|
|
708
|
+
...(workstream.agentId ? { agentId: workstream.agentId } : {}),
|
|
709
|
+
title: workstream.title,
|
|
710
|
+
status: workstream.status,
|
|
711
|
+
nameGenerated: workstream.nameGenerated,
|
|
712
|
+
isRunning: workstream.isRunning,
|
|
713
|
+
isCompacting: workstream.isCompacting,
|
|
714
|
+
createdAt: workstream.createdAt,
|
|
715
|
+
updatedAt: workstream.updatedAt,
|
|
738
716
|
}
|
|
739
717
|
}
|
|
740
718
|
|
|
@@ -745,7 +723,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
745
723
|
SET turnCount += 1
|
|
746
724
|
RETURN turnCount
|
|
747
725
|
`)
|
|
748
|
-
return result[0]
|
|
726
|
+
return result[0].turnCount
|
|
749
727
|
}
|
|
750
728
|
|
|
751
729
|
async persistGeneratedTitle(workstreamId: RecordIdRef, title: string): Promise<void> {
|
|
@@ -759,7 +737,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
759
737
|
if (workstream.mode === 'direct') {
|
|
760
738
|
throw new Error(`Direct workstreams cannot be ${action}d`)
|
|
761
739
|
}
|
|
762
|
-
if (workstream.core
|
|
740
|
+
if (workstream.core) {
|
|
763
741
|
throw new Error(`Core workstreams cannot be ${action}d`)
|
|
764
742
|
}
|
|
765
743
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sdkWorkstreamStatusSchema } from '@lota-sdk/shared'
|
|
1
|
+
import { recordIdSchema, sdkWorkstreamStatusSchema } from '@lota-sdk/shared'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
const WorkstreamModeSchema = z.enum(['direct', 'group'])
|
|
@@ -23,27 +23,27 @@ export interface NormalizedWorkstream {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export const WorkstreamSchema = z.object({
|
|
26
|
-
id:
|
|
27
|
-
mode: WorkstreamModeSchema
|
|
28
|
-
core: z.boolean()
|
|
26
|
+
id: recordIdSchema,
|
|
27
|
+
mode: WorkstreamModeSchema,
|
|
28
|
+
core: z.boolean(),
|
|
29
29
|
coreType: CoreWorkstreamTypeSchema.nullish(),
|
|
30
30
|
agentId: z.string().nullish(),
|
|
31
31
|
title: z.string().nullish(),
|
|
32
|
-
status: sdkWorkstreamStatusSchema
|
|
32
|
+
status: sdkWorkstreamStatusSchema,
|
|
33
33
|
memoryBlock: z.string().nullish(),
|
|
34
34
|
memoryBlockSummary: z.string().nullish(),
|
|
35
35
|
activeRunId: z.string().nullish(),
|
|
36
36
|
activeStreamId: z.string().nullish(),
|
|
37
37
|
compactionSummary: z.string().nullish(),
|
|
38
38
|
lastCompactedMessageId: z.string().nullish(),
|
|
39
|
-
nameGenerated: z.boolean()
|
|
39
|
+
nameGenerated: z.boolean(), // Ideally `isNameGenerated`, but maps directly to SurrealDB column `nameGenerated`
|
|
40
40
|
isCompacting: z.boolean().optional(),
|
|
41
41
|
state: z.unknown().optional(),
|
|
42
|
-
turnCount: z.number().int()
|
|
42
|
+
turnCount: z.number().int(),
|
|
43
43
|
createdAt: z.coerce.date(),
|
|
44
44
|
updatedAt: z.coerce.date(),
|
|
45
|
-
userId:
|
|
46
|
-
organizationId:
|
|
45
|
+
userId: recordIdSchema,
|
|
46
|
+
organizationId: recordIdSchema,
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
export type WorkstreamRecord = z.infer<typeof WorkstreamSchema>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PlanDataSchema, PlanNodeSpec, PlanSchemaRegistry, WriteIntent } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { validateSchemaValue } from './plan-validator.service'
|
|
4
|
+
|
|
5
|
+
export interface WriteValidationIssue {
|
|
6
|
+
code: string
|
|
7
|
+
message: string
|
|
8
|
+
path?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WriteValidationResult {
|
|
12
|
+
status: 'pass' | 'fail'
|
|
13
|
+
issues: WriteValidationIssue[]
|
|
14
|
+
suggestion?: string
|
|
15
|
+
validatedAt: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class WriteIntentValidatorService {
|
|
19
|
+
validate(params: {
|
|
20
|
+
intent: WriteIntent
|
|
21
|
+
nodeSpec: PlanNodeSpec
|
|
22
|
+
schemaRegistry: PlanSchemaRegistry
|
|
23
|
+
existingDeliverables: Map<string, unknown>
|
|
24
|
+
}): WriteValidationResult {
|
|
25
|
+
const issues: WriteValidationIssue[] = []
|
|
26
|
+
const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
|
|
27
|
+
|
|
28
|
+
if (intent.targetPath.startsWith('structuredOutput')) {
|
|
29
|
+
if (nodeSpec.outputSchemaRef) {
|
|
30
|
+
const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
|
|
31
|
+
if (schema) {
|
|
32
|
+
const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
|
|
33
|
+
for (const message of schemaIssues) {
|
|
34
|
+
issues.push({ code: 'schema_validation_failed', message })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.buildResult(issues)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
|
|
42
|
+
if (!deliverable) {
|
|
43
|
+
issues.push({
|
|
44
|
+
code: 'unknown_deliverable',
|
|
45
|
+
message: `"${intent.targetPath}" does not match any declared deliverable.`,
|
|
46
|
+
})
|
|
47
|
+
return this.buildResult(issues)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (deliverable.schemaRef) {
|
|
51
|
+
const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
|
|
52
|
+
if (schema) {
|
|
53
|
+
const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
|
|
54
|
+
for (const message of schemaIssues) {
|
|
55
|
+
issues.push({ code: 'schema_validation_failed', message })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
|
|
61
|
+
issues.push({
|
|
62
|
+
code: 'update_target_not_found',
|
|
63
|
+
message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this.buildResult(issues)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private buildResult(issues: WriteValidationIssue[]): WriteValidationResult {
|
|
71
|
+
const hasFailed = issues.length > 0
|
|
72
|
+
return {
|
|
73
|
+
status: hasFailed ? 'fail' : 'pass',
|
|
74
|
+
issues,
|
|
75
|
+
...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
|
|
76
|
+
validatedAt: new Date().toISOString(),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const writeIntentValidatorService = new WriteIntentValidatorService()
|
|
@@ -64,7 +64,7 @@ export async function extractAttachmentText(file: File): Promise<string> {
|
|
|
64
64
|
return normalizeExtractedText((await extractPdfPages(file)).join('\n\n'))
|
|
65
65
|
}
|
|
66
66
|
if (isDocxAttachmentFile(file)) {
|
|
67
|
-
return
|
|
67
|
+
return extractDocxText(file)
|
|
68
68
|
}
|
|
69
69
|
return ''
|
|
70
70
|
}
|
|
@@ -3,7 +3,7 @@ function sanitizeFilename(name: string): string {
|
|
|
3
3
|
return ascii.replace(/[\\/:"*?<>|]+/g, '-').trim()
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
function toSafeSegment(value: string): string {
|
|
6
|
+
export function toSafeSegment(value: string): string {
|
|
7
7
|
const cleaned = value.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
8
8
|
return cleaned.length > 0 ? cleaned : 'unknown'
|
|
9
9
|
}
|