@lota-sdk/core 0.1.31 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "@chat-adapter/slack": "^4.23.0",
33
33
  "@chat-adapter/state-ioredis": "^4.23.0",
34
34
  "@logtape/logtape": "^2.0.5",
35
- "@lota-sdk/shared": "0.1.31",
35
+ "@lota-sdk/shared": "0.1.33",
36
36
  "@mendable/firecrawl-js": "^4.18.0",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.141",
@@ -18,7 +18,6 @@ export interface AgentRuntimeConfig<TAgent extends string> {
18
18
  }
19
19
 
20
20
  export interface AgentRuntimeRuleOptions {
21
- includeExecutionPlanRule?: boolean
22
21
  includeMemr3Rule?: boolean
23
22
  includeDomainReasoningFallbackRule?: boolean
24
23
  }
@@ -36,17 +35,14 @@ export interface AgentToolPolicy<TSkill extends PropertyKey> {
36
35
  includeProceedInOnboarding: boolean
37
36
  includeGithubIntegration: boolean
38
37
  includeIndexRepositoryByURL: boolean
38
+ includeExecutionPlanTools: boolean
39
39
  includeIndexedRepository: boolean
40
40
  }
41
41
 
42
42
  export const OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES = Object.freeze([
43
43
  'conversationSearch',
44
- 'createExecutionPlan',
45
- 'replaceExecutionPlan',
46
- 'submitExecutionNodeResult',
47
- 'listExecutionPlans',
48
- 'getExecutionPlanDetails',
49
- 'resumeExecutionPlanRun',
44
+ 'executionPlan',
45
+ 'executionPlanQuery',
50
46
  'consultSpecialist',
51
47
  'consultTeam',
52
48
  'teamThink',
@@ -184,12 +180,13 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
184
180
  return {
185
181
  resolvedMode,
186
182
  skills,
187
- includeMemorySearch: true,
188
- includeConversationSearch: true,
189
- includeMemoryRemember: true,
190
- includeOrgActionSearch: true,
183
+ includeMemorySearch: !params.onboardingActive,
184
+ includeConversationSearch: !params.onboardingActive,
185
+ includeMemoryRemember: !params.onboardingActive,
186
+ includeOrgActionSearch: !params.onboardingActive,
191
187
  includeMemoryBlockAppend: true,
192
- includeReadFileParts: true,
188
+ includeReadFileParts: !params.onboardingActive,
189
+ includeExecutionPlanTools: !params.onboardingActive,
193
190
  includeInspectWebsite: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
194
191
  includeProceedInOnboarding: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
195
192
  includeGithubIntegration: params.agentId === onboardingOwnerAgentId,
@@ -216,6 +213,7 @@ export function buildTeamConsultationAgentToolPolicy({
216
213
  includeOrgActionSearch: false,
217
214
  includeMemoryBlockAppend: false,
218
215
  includeReadFileParts: false,
216
+ includeExecutionPlanTools: false,
219
217
  includeInspectWebsite: false,
220
218
  includeProceedInOnboarding: false,
221
219
  includeGithubIntegration: false,
@@ -60,14 +60,7 @@ export function isApprovalContinuationRequest(messages: ChatMessage[]): boolean
60
60
  return hasApprovalRespondedParts(messages[lastAssistantIndex])
61
61
  }
62
62
 
63
- const PLAN_TOOL_NAMES = new Set([
64
- 'createExecutionPlan',
65
- 'replaceExecutionPlan',
66
- 'submitExecutionNodeResult',
67
- 'listExecutionPlans',
68
- 'getExecutionPlanDetails',
69
- 'resumeExecutionPlanRun',
70
- ])
63
+ const PLAN_TOOL_NAMES = new Set(['executionPlan', 'executionPlanQuery'])
71
64
 
72
65
  function isPlanApprovalId(value: unknown): value is string {
73
66
  return typeof value === 'string' && value.startsWith(`${TABLES.PLAN_APPROVAL}:`)
@@ -85,7 +85,6 @@ export async function runSocialAgentTurn(params: {
85
85
  learnedSkillsSection: params.learnedSkillsSection,
86
86
  userMessageText: params.userMessageText,
87
87
  ruleOptions: {
88
- includeExecutionPlanRule: false,
89
88
  includeMemr3Rule: SOCIAL_CHAT_RETRIEVAL_TOOL_NAMES.some((toolName) => toolName in params.tools),
90
89
  includeDomainReasoningFallbackRule: params.agentId === 'cpo' || params.agentId === 'mentor',
91
90
  },
@@ -342,11 +342,7 @@ async function streamAgentResponse(
342
342
  workstreamStateSection,
343
343
  learnedSkillsSection,
344
344
  userMessageText: latestUserMessageText,
345
- ruleOptions: {
346
- includeExecutionPlanRule: streamParams.includeExecutionPlanTools,
347
- includeMemr3Rule: hasRetrievalTools,
348
- includeDomainReasoningFallbackRule: hasDomainRoutingSkills,
349
- },
345
+ ruleOptions: { includeMemr3Rule: hasRetrievalTools, includeDomainReasoningFallbackRule: hasDomainRoutingSkills },
350
346
  additionalInstructionSections: mergeInstructionSections(
351
347
  executionPlanInstructionSections,
352
348
  streamParams.additionalInstructionSections,
@@ -363,6 +359,7 @@ async function streamAgentResponse(
363
359
  mode: streamParams.mode,
364
360
  tools: streamParams.tools,
365
361
  extraInstructions: config.extraInstructions,
362
+ maxRetries: 3,
366
363
  stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
367
364
  streamParams.stopWhen ?? [stepCountIs(config.maxSteps as number)],
368
365
  prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
@@ -370,19 +367,32 @@ async function streamAgentResponse(
370
367
  const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
371
368
  agentTimer.step('agent-construction')
372
369
 
370
+ const MAX_STREAM_RETRIES = 3
373
371
  let result: unknown
374
- try {
375
- result = await streamParams.observer.run(() =>
376
- agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
377
- )
378
- agentTimer.step('agent.stream()-resolved')
379
- } catch (error) {
380
- if (agentAbortSignal.aborted) {
381
- streamParams.observer.recordAbort(error)
382
- } else {
383
- streamParams.observer.recordError(error)
372
+ for (let attempt = 0; ; attempt++) {
373
+ try {
374
+ result = await streamParams.observer.run(() =>
375
+ agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal }),
376
+ )
377
+ agentTimer.step('agent.stream()-resolved')
378
+ break
379
+ } catch (error) {
380
+ if (agentAbortSignal.aborted) {
381
+ streamParams.observer.recordAbort(error)
382
+ throw error
383
+ }
384
+ const errorMessage = error instanceof Error ? error.message : String(error)
385
+ const isTransient =
386
+ errorMessage.includes('client disconnected') ||
387
+ errorMessage.includes('ECONNRESET') ||
388
+ errorMessage.includes('socket hang up') ||
389
+ errorMessage.includes('fetch failed')
390
+ if (!isTransient || attempt >= MAX_STREAM_RETRIES - 1) {
391
+ streamParams.observer.recordError(error)
392
+ throw error
393
+ }
394
+ aiLogger.warn`Transient stream error (attempt ${attempt + 1}/${MAX_STREAM_RETRIES}): ${errorMessage} — retrying`
384
395
  }
385
- throw error
386
396
  }
387
397
  if (!hasUIMessageStream(result)) {
388
398
  throw new Error(`Agent run for ${resolvedAgentId} did not expose a UI message stream.`)
@@ -1,65 +1,157 @@
1
1
  import {
2
- CreateExecutionPlanArgsSchema,
3
- GetActiveExecutionPlanArgsSchema,
4
- ListExecutionPlansArgsSchema,
5
- ResumeExecutionPlanRunArgsSchema,
6
- ReplaceExecutionPlanArgsSchema,
2
+ ExecutionPlanArgsSchema,
3
+ ExecutionPlanQueryArgsSchema,
7
4
  SubmitExecutionNodeResultArgsSchema,
8
5
  expandAgentPlanDraft,
9
6
  getLatestExecutionPlanResult,
10
7
  } from '@lota-sdk/shared'
8
+ import type { ExecutionPlanAction, ExecutionPlanArgs, CreateProjectWithPlanResultData } from '@lota-sdk/shared'
11
9
  import { tool } from 'ai'
12
10
 
13
11
  import type { RecordIdRef } from '../db/record-id'
12
+ import { recordIdToString } from '../db/record-id'
13
+ import { TABLES } from '../db/tables'
14
14
  import { executionPlanService } from '../services/execution-plan.service'
15
+ import { workstreamService } from '../services/workstream.service'
15
16
 
16
- export function createCreateExecutionPlanTool(params: {
17
+ type ExecutionPlanWorkstreamService = Pick<
18
+ typeof workstreamService,
19
+ 'createWorkstream' | 'deleteWorkstream' | 'getWorkstream'
20
+ >
21
+
22
+ type ExecutionPlanExecutionPlanService = Pick<
23
+ typeof executionPlanService,
24
+ | 'createPlan'
25
+ | 'replacePlan'
26
+ | 'resumeRun'
27
+ | 'listActivePlanSummaries'
28
+ | 'getActivePlanToolResult'
29
+ | 'getActivePlansForWorkstream'
30
+ >
31
+
32
+ function extractDraft(input: ExecutionPlanArgs) {
33
+ const { action, projectTitle, targetWorkstreamId, runId, reason, ...draftInput } = input
34
+ return { action, projectTitle, targetWorkstreamId, runId, reason, draft: expandAgentPlanDraft(draftInput) }
35
+ }
36
+
37
+ export function createExecutionPlanTool(params: {
17
38
  orgId: RecordIdRef
39
+ userId: RecordIdRef
18
40
  workstreamId: RecordIdRef
19
41
  agentId: string
42
+ executionPlanService?: ExecutionPlanExecutionPlanService
43
+ workstreamService?: ExecutionPlanWorkstreamService
20
44
  onPlanChanged?: () => void
21
45
  validateInlinePlan?: (draft: ReturnType<typeof expandAgentPlanDraft>) => void
22
46
  }) {
47
+ const resolvedEpService = params.executionPlanService ?? executionPlanService
48
+ const resolvedWsService = params.workstreamService ?? workstreamService
49
+
23
50
  return tool({
24
51
  description:
25
- 'Create a contract-driven execution plan for this workstream. Use createProjectWithPlan instead when the work needs a dedicated project workstream or a larger 3+ node plan.',
26
- inputSchema: CreateExecutionPlanArgsSchema,
52
+ 'Manage execution plans. Actions: create (inline, 1-2 nodes), create-project (dedicated project workstream, 3+ nodes), replace (swap active plan), resume (resume interrupted plan).',
53
+ inputSchema: ExecutionPlanArgsSchema,
27
54
  execute: async (input) => {
28
- const { targetWorkstreamId, ...draftInput } = input
29
- const draft = expandAgentPlanDraft(draftInput)
30
- params.validateInlinePlan?.(draft)
31
- const result = await executionPlanService.createPlan({
32
- organizationId: params.orgId,
33
- workstreamId: targetWorkstreamId ?? params.workstreamId,
34
- leadAgentId: params.agentId,
35
- input: draft,
36
- })
55
+ const { action, projectTitle, targetWorkstreamId, runId, reason, draft } = extractDraft(input)
56
+
57
+ const handler: Record<ExecutionPlanAction, () => Promise<unknown>> = {
58
+ create: async () => {
59
+ params.validateInlinePlan?.(draft)
60
+ return await resolvedEpService.createPlan({
61
+ organizationId: params.orgId,
62
+ workstreamId: targetWorkstreamId ?? params.workstreamId,
63
+ leadAgentId: params.agentId,
64
+ input: draft,
65
+ })
66
+ },
67
+
68
+ 'create-project': async () => {
69
+ const targetWorkstream = targetWorkstreamId
70
+ ? await resolvedWsService.getWorkstream(targetWorkstreamId)
71
+ : await resolvedWsService.createWorkstream(params.userId, params.orgId, {
72
+ title: projectTitle!,
73
+ mode: 'group',
74
+ })
75
+
76
+ if (targetWorkstream.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
77
+ throw new Error('Target workstream belongs to a different organization.')
78
+ }
79
+ if (targetWorkstream.userId !== recordIdToString(params.userId, TABLES.USER)) {
80
+ throw new Error('Target workstream belongs to a different user.')
81
+ }
82
+
83
+ const existingPlans = await resolvedEpService.getActivePlansForWorkstream(targetWorkstream.id)
84
+ if (!targetWorkstream.core && existingPlans.length > 0) {
85
+ throw new Error(
86
+ 'This workstream already has an active execution plan. Use action "replace" or target a core workstream.',
87
+ )
88
+ }
89
+
90
+ const createdWorkstream = !targetWorkstreamId
91
+ try {
92
+ const result = await resolvedEpService.createPlan({
93
+ organizationId: params.orgId,
94
+ workstreamId: targetWorkstream.id,
95
+ leadAgentId: params.agentId,
96
+ input: draft,
97
+ })
98
+ return {
99
+ ...result,
100
+ workstreamId: targetWorkstream.id,
101
+ workstreamTitle: targetWorkstream.title,
102
+ createdWorkstream,
103
+ } satisfies CreateProjectWithPlanResultData
104
+ } catch (error) {
105
+ if (createdWorkstream) {
106
+ await resolvedWsService.deleteWorkstream(targetWorkstream.id).catch(() => {})
107
+ }
108
+ throw error
109
+ }
110
+ },
111
+
112
+ replace: async () => {
113
+ return await resolvedEpService.replacePlan({
114
+ organizationId: params.orgId,
115
+ workstreamId: params.workstreamId,
116
+ leadAgentId: params.agentId,
117
+ input: { runId: runId!, reason: reason!, ...draft },
118
+ })
119
+ },
120
+
121
+ resume: async () => {
122
+ return await resolvedEpService.resumeRun({
123
+ workstreamId: params.workstreamId,
124
+ emittedBy: params.agentId,
125
+ input: { runId: runId! },
126
+ })
127
+ },
128
+ }
129
+
130
+ const result = await handler[action]()
37
131
  params.onPlanChanged?.()
38
132
  return result
39
133
  },
40
134
  })
41
135
  }
42
136
 
43
- export function createReplaceExecutionPlanTool(params: {
44
- orgId: RecordIdRef
45
- workstreamId: RecordIdRef
46
- agentId: string
47
- onPlanChanged?: () => void
48
- }) {
137
+ export function createExecutionPlanQueryTool(params: { workstreamId: RecordIdRef }) {
49
138
  return tool({
50
139
  description:
51
- 'Replace the active execution run with a new contract-driven plan when the graph, contracts, or approach materially change.',
52
- inputSchema: ReplaceExecutionPlanArgsSchema,
140
+ 'Query execution plans. Omit runId to list all active plans. Provide runId to load a specific plan run.',
141
+ inputSchema: ExecutionPlanQueryArgsSchema,
53
142
  execute: async (input) => {
54
- const { runId, reason, ...draftInput } = input
55
- const result = await executionPlanService.replacePlan({
56
- organizationId: params.orgId,
143
+ if (!input.runId) {
144
+ return await executionPlanService.listActivePlanSummaries(params.workstreamId)
145
+ }
146
+ return await executionPlanService.getActivePlanToolResult({
57
147
  workstreamId: params.workstreamId,
58
- leadAgentId: params.agentId,
59
- input: { runId, reason, ...expandAgentPlanDraft(draftInput) },
148
+ runId: input.runId,
149
+ includeEvents: input.includeEvents,
150
+ includeArtifacts: input.includeArtifacts,
151
+ includeApprovals: input.includeApprovals,
152
+ includeCheckpoints: input.includeCheckpoints,
153
+ includeValidationIssues: input.includeValidationIssues,
60
154
  })
61
- params.onPlanChanged?.()
62
- return result
63
155
  },
64
156
  })
65
157
  }
@@ -89,50 +181,3 @@ export function createSubmitExecutionNodeResultTool(params: {
89
181
  },
90
182
  })
91
183
  }
92
-
93
- export function createListExecutionPlansTool(params: { workstreamId: RecordIdRef }) {
94
- return tool({
95
- description:
96
- 'List all active execution plans for this workstream with summary info (title, status, objective, node counts). Use getExecutionPlanDetails to inspect a specific plan.',
97
- inputSchema: ListExecutionPlansArgsSchema,
98
- execute: async () => await executionPlanService.listActivePlanSummaries(params.workstreamId),
99
- })
100
- }
101
-
102
- export function createGetExecutionPlanDetailsTool(params: { workstreamId: RecordIdRef }) {
103
- return tool({
104
- description: 'Load a plan run and its current state.',
105
- inputSchema: GetActiveExecutionPlanArgsSchema,
106
- execute: async (input) =>
107
- await executionPlanService.getActivePlanToolResult({
108
- workstreamId: params.workstreamId,
109
- runId: input.runId,
110
- includeEvents: input.includeEvents,
111
- includeArtifacts: input.includeArtifacts,
112
- includeApprovals: input.includeApprovals,
113
- includeCheckpoints: input.includeCheckpoints,
114
- includeValidationIssues: input.includeValidationIssues,
115
- }),
116
- })
117
- }
118
-
119
- export function createResumeExecutionPlanRunTool(params: {
120
- workstreamId: RecordIdRef
121
- agentId: string
122
- onPlanChanged?: () => void
123
- }) {
124
- return tool({
125
- description:
126
- 'Resume the active execution run from its latest checkpoint after interruption. The executor reconciles state before re-activating nodes.',
127
- inputSchema: ResumeExecutionPlanRunArgsSchema,
128
- execute: async (input) => {
129
- const result = await executionPlanService.resumeRun({
130
- workstreamId: params.workstreamId,
131
- emittedBy: params.agentId,
132
- input,
133
- })
134
- params.onPlanChanged?.()
135
- return result
136
- },
137
- })
138
- }
@@ -1,7 +1,6 @@
1
1
  export * from './execution-plan.tool'
2
2
  export * from './fetch-webpage.tool'
3
3
  export * from './memory-block.tool'
4
- export * from './project-with-plan.tool'
5
4
  export * from './read-file-parts.tool'
6
5
  export * from './remember-memory.tool'
7
6
  export * from './research-topic.tool'
@@ -1,87 +0,0 @@
1
- import type { CreateProjectWithPlanResultData } from '@lota-sdk/shared'
2
- import { CreateProjectWithPlanArgsSchema, expandAgentPlanDraft } from '@lota-sdk/shared'
3
- import { tool } from 'ai'
4
-
5
- import type { RecordIdRef } from '../db/record-id'
6
- import { recordIdToString } from '../db/record-id'
7
- import { TABLES } from '../db/tables'
8
- import { executionPlanService } from '../services/execution-plan.service'
9
- import { workstreamService } from '../services/workstream.service'
10
-
11
- type ProjectWithPlanWorkstreamService = Pick<
12
- typeof workstreamService,
13
- 'createWorkstream' | 'deleteWorkstream' | 'getWorkstream'
14
- >
15
-
16
- type ProjectWithPlanExecutionPlanService = Pick<
17
- typeof executionPlanService,
18
- 'createPlan' | 'getActivePlansForWorkstream'
19
- >
20
-
21
- export function createCreateProjectWithPlanTool(params: {
22
- orgId: RecordIdRef
23
- userId: RecordIdRef
24
- agentId: string
25
- workstreamService?: ProjectWithPlanWorkstreamService
26
- executionPlanService?: ProjectWithPlanExecutionPlanService
27
- onPlanChanged?: () => void
28
- }) {
29
- const resolvedWorkstreamService = params.workstreamService ?? workstreamService
30
- const resolvedExecutionPlanService = params.executionPlanService ?? executionPlanService
31
-
32
- return tool({
33
- description:
34
- 'Create a dedicated project workstream with an execution plan, or add a simplified agent-authored plan to an existing target workstream.',
35
- inputSchema: CreateProjectWithPlanArgsSchema,
36
- execute: async (input): Promise<CreateProjectWithPlanResultData> => {
37
- const { projectTitle, targetWorkstreamId, ...draftInput } = input
38
- const expandedDraft = expandAgentPlanDraft(draftInput)
39
-
40
- const targetWorkstream = targetWorkstreamId
41
- ? await resolvedWorkstreamService.getWorkstream(targetWorkstreamId)
42
- : await resolvedWorkstreamService.createWorkstream(params.userId, params.orgId, {
43
- title: projectTitle,
44
- mode: 'group',
45
- })
46
-
47
- if (targetWorkstream.organizationId !== recordIdToString(params.orgId, TABLES.ORGANIZATION)) {
48
- throw new Error('Target workstream belongs to a different organization.')
49
- }
50
- if (targetWorkstream.userId !== recordIdToString(params.userId, TABLES.USER)) {
51
- throw new Error('Target workstream belongs to a different user.')
52
- }
53
-
54
- const existingPlans = await resolvedExecutionPlanService.getActivePlansForWorkstream(targetWorkstream.id)
55
- if (!targetWorkstream.core && existingPlans.length > 0) {
56
- throw new Error(
57
- 'This workstream already has an active execution plan. Use replaceExecutionPlan or target a core workstream.',
58
- )
59
- }
60
-
61
- const createdWorkstream = !targetWorkstreamId
62
-
63
- try {
64
- const result = await resolvedExecutionPlanService.createPlan({
65
- organizationId: params.orgId,
66
- workstreamId: targetWorkstream.id,
67
- leadAgentId: params.agentId,
68
- input: expandedDraft,
69
- })
70
-
71
- params.onPlanChanged?.()
72
-
73
- return {
74
- ...result,
75
- workstreamId: targetWorkstream.id,
76
- workstreamTitle: targetWorkstream.title,
77
- createdWorkstream,
78
- }
79
- } catch (error) {
80
- if (createdWorkstream) {
81
- await resolvedWorkstreamService.deleteWorkstream(targetWorkstream.id).catch(() => {})
82
- }
83
- throw error
84
- }
85
- },
86
- })
87
- }