@lota-sdk/core 0.2.3 → 0.3.1
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 +2 -2
- package/infrastructure/schema/00_thread.surql +73 -0
- package/infrastructure/schema/02_execution_plan.surql +10 -11
- package/infrastructure/schema/04_runtime_bootstrap.surql +1 -0
- package/infrastructure/schema/10_autonomous_job.surql +3 -3
- package/package.json +2 -2
- package/src/ai/definitions.ts +1 -1
- package/src/config/agent-defaults.ts +5 -5
- package/src/config/index.ts +1 -1
- package/src/config/thread-defaults.ts +72 -0
- package/src/create-runtime.ts +90 -94
- package/src/db/record-id.ts +21 -21
- package/src/db/service.ts +44 -40
- package/src/db/tables.ts +3 -3
- package/src/db/{workstream-message-row.ts → thread-message-row.ts} +3 -3
- package/src/queues/context-compaction.queue.ts +6 -6
- package/src/queues/plan-agent-heartbeat.queue.ts +3 -3
- package/src/queues/post-chat-memory.queue.ts +1 -1
- package/src/queues/title-generation.queue.ts +10 -13
- package/src/redis/index.ts +1 -1
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/agent-identity-overrides.ts +1 -1
- package/src/runtime/agent-runtime-policy.ts +19 -21
- package/src/runtime/chat-request-routing.ts +1 -1
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction.ts +1 -1
- package/src/runtime/execution-plan.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src/runtime/memory-digest-policy.ts +1 -1
- package/src/runtime/plugin-types.ts +1 -1
- package/src/runtime/post-turn-side-effects.ts +35 -35
- package/src/runtime/runtime-config.ts +24 -21
- package/src/runtime/runtime-extensions.ts +11 -11
- package/src/runtime/social-chat-agent-runner.ts +3 -3
- package/src/runtime/social-chat-history.ts +1 -1
- package/src/runtime/social-chat.ts +6 -6
- package/src/runtime/team-consultation-orchestrator.ts +1 -1
- package/src/runtime/{workstream-chat-helpers.ts → thread-chat-helpers.ts} +7 -7
- package/src/runtime/{workstream-plan-turn.ts → thread-plan-turn.ts} +11 -17
- package/src/runtime/{workstream-turn-context.ts → thread-turn-context.ts} +10 -10
- package/src/services/agent-activity.service.ts +39 -44
- package/src/services/agent-executor.service.ts +17 -19
- package/src/services/attachment.service.ts +4 -8
- package/src/services/autonomous-job.service.ts +29 -28
- package/src/services/context-compaction.service.ts +19 -29
- package/src/services/execution-plan.service.ts +58 -70
- package/src/services/global-orchestrator.service.ts +5 -5
- package/src/services/index.ts +6 -6
- package/src/services/memory.service.ts +1 -1
- package/src/services/monitoring-window.service.ts +2 -2
- package/src/services/mutating-approval.service.ts +7 -10
- package/src/services/node-workspace.service.ts +8 -7
- package/src/services/notification.service.ts +1 -1
- package/src/services/organization.service.ts +9 -9
- package/src/services/ownership-dispatcher.service.ts +13 -19
- package/src/services/plan-agent-heartbeat.service.ts +13 -13
- package/src/services/plan-agent-query.service.ts +7 -7
- package/src/services/plan-artifact.service.ts +1 -2
- package/src/services/plan-coordination.service.ts +4 -4
- package/src/services/plan-cycle.service.ts +7 -7
- package/src/services/plan-deadline.service.ts +4 -4
- package/src/services/plan-event-delivery.service.ts +8 -12
- package/src/services/plan-executor.service.ts +25 -39
- package/src/services/plan-run-data.ts +27 -8
- package/src/services/plan-run.service.ts +7 -9
- package/src/services/plan-scheduler.service.ts +4 -4
- package/src/services/plan-template.service.ts +2 -2
- package/src/services/plan-validator.service.ts +0 -11
- package/src/services/plugin-executor.service.ts +1 -1
- package/src/services/queue-job.service.ts +1 -1
- package/src/services/recent-activity-title.service.ts +1 -1
- package/src/services/recent-activity.service.ts +4 -4
- package/src/services/system-executor.service.ts +2 -2
- package/src/services/{workstream-message.service.ts → thread-message.service.ts} +72 -76
- package/src/services/thread-plan-registry.service.ts +22 -0
- package/src/services/thread-title.service.ts +39 -0
- package/src/services/{workstream-turn-preparation.service.ts → thread-turn-preparation.service.ts} +148 -171
- package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
- package/src/services/thread.service.ts +853 -0
- package/src/services/thread.types.ts +17 -0
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/system-agents/index.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/researcher.agent.ts +3 -3
- package/src/system-agents/{workstream-router.agent.ts → thread-router.agent.ts} +68 -135
- package/src/system-agents/title-generator.agent.ts +8 -8
- package/src/tools/execution-plan.tool.ts +39 -40
- package/src/tools/memory-block.tool.ts +4 -4
- package/src/tools/research-topic.tool.ts +1 -0
- package/src/tools/search-web.tool.ts +1 -1
- package/src/tools/search.tool.ts +4 -4
- package/src/tools/team-think.tool.ts +9 -9
- package/src/utils/async.ts +6 -7
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +43 -43
- package/src/workers/skill-extraction.runner.ts +9 -13
- package/src/workers/utils/{workstream-message-query.ts → thread-message-query.ts} +21 -21
- package/infrastructure/schema/00_workstream.surql +0 -64
- package/src/config/workstream-defaults.ts +0 -72
- package/src/services/workstream-plan-registry.service.ts +0 -22
- package/src/services/workstream-title.service.ts +0 -42
- package/src/services/workstream.service.ts +0 -803
- package/src/services/workstream.types.ts +0 -17
- /package/src/services/{workstream-constants.ts → thread-constants.ts} +0 -0
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
extractMessageText,
|
|
49
49
|
toHistoryMessages,
|
|
50
50
|
toOptionalTrimmedString,
|
|
51
|
-
} from './
|
|
51
|
+
} from './thread-chat-helpers'
|
|
52
52
|
|
|
53
53
|
const DEFAULT_SOCIAL_CHAT_AGENT_ID = 'socialChat'
|
|
54
54
|
const DEFAULT_SOCIAL_CHAT_AGENT_DISPLAY_NAME = 'Lota'
|
|
@@ -362,8 +362,8 @@ export function createSocialChatRuntime(params: {
|
|
|
362
362
|
|
|
363
363
|
return await runSocialAgentTurn({
|
|
364
364
|
agentId,
|
|
365
|
-
mode: '
|
|
366
|
-
|
|
365
|
+
mode: 'fixedThreadMode',
|
|
366
|
+
threadType: 'group',
|
|
367
367
|
onboardingActive: lifecycleState?.bootstrapActive ?? false,
|
|
368
368
|
linearInstalled: false,
|
|
369
369
|
systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
|
|
@@ -407,8 +407,8 @@ export function createSocialChatRuntime(params: {
|
|
|
407
407
|
aiLogger.info`Slack social-chat generating reply: channelId=${messageContext.channelId}, threadId=${messageContext.threadId}`
|
|
408
408
|
const leadRun = await runSocialAgentTurn({
|
|
409
409
|
agentId: socialAgentId,
|
|
410
|
-
mode: '
|
|
411
|
-
|
|
410
|
+
mode: 'threadMode',
|
|
411
|
+
threadType: 'group',
|
|
412
412
|
onboardingActive: lifecycleState?.bootstrapActive ?? false,
|
|
413
413
|
linearInstalled: false,
|
|
414
414
|
systemWorkspaceDetails: promptContext.systemWorkspaceDetails,
|
|
@@ -453,7 +453,7 @@ export function createSocialChatRuntime(params: {
|
|
|
453
453
|
enqueuePostChatMemory(
|
|
454
454
|
{
|
|
455
455
|
orgId: workspaceIdString,
|
|
456
|
-
|
|
456
|
+
threadId: `social:slack:${messageContext.threadId}`,
|
|
457
457
|
sourceId: createSocialChatCursorId({
|
|
458
458
|
workspaceId: workspaceIdString,
|
|
459
459
|
threadId: messageContext.threadId,
|
|
@@ -8,7 +8,7 @@ import { buildModelInputMessagesWithUploadMetadata, buildReadableUploadMetadataT
|
|
|
8
8
|
import type { ReadableUploadMetadataLike } from './chat-attachments'
|
|
9
9
|
import type { RepoSectionName } from './indexed-repositories-policy'
|
|
10
10
|
import { buildTeamConsultationFailureMessage } from './team-consultation-prompts'
|
|
11
|
-
import { extractMessageText } from './
|
|
11
|
+
import { extractMessageText } from './thread-chat-helpers'
|
|
12
12
|
|
|
13
13
|
export type DefaultRepoSections = RepoSectionName[]
|
|
14
14
|
const TEAM_CONSULTATION_TIMEOUT_MS = 90_000
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { agentDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
|
|
2
2
|
import type { ChatMessageLike, ReadableUploadMetadataLike } from './chat-types'
|
|
3
3
|
|
|
4
|
-
export interface
|
|
4
|
+
export interface ThreadHistoryMessage {
|
|
5
5
|
role: 'user' | 'agent'
|
|
6
6
|
content: string
|
|
7
7
|
agentName?: string
|
|
@@ -42,9 +42,9 @@ export function extractMessageText(message: ChatMessageLike): string {
|
|
|
42
42
|
.trim()
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24):
|
|
45
|
+
export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): ThreadHistoryMessage[] {
|
|
46
46
|
return messages
|
|
47
|
-
.map((message):
|
|
47
|
+
.map((message): ThreadHistoryMessage | null => {
|
|
48
48
|
const content = extractMessageText(message)
|
|
49
49
|
if (!content) return null
|
|
50
50
|
|
|
@@ -57,7 +57,7 @@ export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): W
|
|
|
57
57
|
}
|
|
58
58
|
return null
|
|
59
59
|
})
|
|
60
|
-
.filter((message): message is
|
|
60
|
+
.filter((message): message is ThreadHistoryMessage => message !== null)
|
|
61
61
|
.slice(-maxItems)
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -86,10 +86,10 @@ export function buildAgentHistoryMessages(messages: ChatMessageLike[]): Array<{
|
|
|
86
86
|
.map((message) => ({ content: message.content, ...(message.agentName ? { agentName: message.agentName } : {}) }))
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export function
|
|
90
|
-
historyMessages:
|
|
89
|
+
export function appendPersistedThreadContextToHistoryMessages(
|
|
90
|
+
historyMessages: ThreadHistoryMessage[],
|
|
91
91
|
params: { compactionSummary?: string | null },
|
|
92
|
-
):
|
|
92
|
+
): ThreadHistoryMessage[] {
|
|
93
93
|
const nextHistoryMessages = [...historyMessages]
|
|
94
94
|
const compactionSummary = typeof params.compactionSummary === 'string' ? params.compactionSummary.trim() : ''
|
|
95
95
|
if (compactionSummary) {
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { SUBMIT_PLAN_TURN_RESULT_TOOL_NAME, withMessageCreatedAt } from '@lota-sdk/shared'
|
|
2
|
-
import type {
|
|
3
|
-
ChatMessage,
|
|
4
|
-
PlanArtifactSubmission,
|
|
5
|
-
PlanNodeHandoffContext,
|
|
6
|
-
PlanNodeRunRecord,
|
|
7
|
-
PlanNodeSpecRecord,
|
|
8
|
-
} from '@lota-sdk/shared'
|
|
2
|
+
import type { ChatMessage, PlanArtifactSubmission, PlanNodeRunRecord, PlanNodeSpecRecord } from '@lota-sdk/shared'
|
|
9
3
|
|
|
10
4
|
import { buildCompletionCheckStructuredOutputHints } from './agent-runtime-policy'
|
|
11
5
|
import { mergeInstructionSections } from './instruction-sections'
|
|
@@ -15,10 +9,10 @@ export interface PlanTurnUpstreamHandoff {
|
|
|
15
9
|
label: string
|
|
16
10
|
ownerRef: string
|
|
17
11
|
ownerType: PlanNodeSpecRecord['owner']['executorType']
|
|
18
|
-
|
|
12
|
+
summary?: string
|
|
19
13
|
}
|
|
20
14
|
|
|
21
|
-
export interface
|
|
15
|
+
export interface ThreadPlanTurnContext {
|
|
22
16
|
runId: string
|
|
23
17
|
nodeId: string
|
|
24
18
|
planTitle: string
|
|
@@ -29,7 +23,7 @@ export interface WorkstreamPlanTurnContext {
|
|
|
29
23
|
upstreamHandoffs: PlanTurnUpstreamHandoff[]
|
|
30
24
|
}
|
|
31
25
|
|
|
32
|
-
function buildPlanTurnExecutionSection(planTurn:
|
|
26
|
+
function buildPlanTurnExecutionSection(planTurn: ThreadPlanTurnContext): string {
|
|
33
27
|
const requiredDeliverables = planTurn.nodeSpec.deliverables.filter((deliverable) => deliverable.required)
|
|
34
28
|
const completionCheckOutputHints = buildCompletionCheckStructuredOutputHints(planTurn.nodeSpec)
|
|
35
29
|
const payload = {
|
|
@@ -55,11 +49,11 @@ function buildPlanTurnExecutionSection(planTurn: WorkstreamPlanTurnContext): str
|
|
|
55
49
|
return [
|
|
56
50
|
'<plan-turn-execution>',
|
|
57
51
|
`Complete node "${planTurn.nodeSpec.label}" for plan "${planTurn.planTitle}".`,
|
|
58
|
-
'Use only the node contract, resolved input, input artifacts, and upstream
|
|
52
|
+
'Use only the node contract, resolved input, input artifacts, and upstream summaries.',
|
|
59
53
|
'Before submitting, satisfy every required deliverable, success criterion, and completion check.',
|
|
60
54
|
`Call ${SUBMIT_PLAN_TURN_RESULT_TOOL_NAME} exactly once when done.`,
|
|
61
55
|
`Required artifacts: ${requiredDeliverables.length > 0 ? requiredDeliverables.map((d) => d.name).join(', ') : 'none'}.`,
|
|
62
|
-
'
|
|
56
|
+
'notes is required. Include a concise summary of what was done, key decisions, and any context downstream nodes need.',
|
|
63
57
|
...(completionCheckOutputHints.length > 0
|
|
64
58
|
? ['Structured output fields required by completion checks:', ...completionCheckOutputHints]
|
|
65
59
|
: []),
|
|
@@ -81,7 +75,7 @@ function buildUpstreamHandoffSection(upstreamHandoffs: PlanTurnUpstreamHandoff[]
|
|
|
81
75
|
label: handoff.label,
|
|
82
76
|
ownerRef: handoff.ownerRef,
|
|
83
77
|
ownerType: handoff.ownerType,
|
|
84
|
-
|
|
78
|
+
...(handoff.summary ? { summary: handoff.summary } : {}),
|
|
85
79
|
})),
|
|
86
80
|
null,
|
|
87
81
|
2,
|
|
@@ -90,7 +84,7 @@ function buildUpstreamHandoffSection(upstreamHandoffs: PlanTurnUpstreamHandoff[]
|
|
|
90
84
|
].join('\n')
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
export function buildPlanTurnInstructionSections(planTurn:
|
|
87
|
+
export function buildPlanTurnInstructionSections(planTurn: ThreadPlanTurnContext): string[] {
|
|
94
88
|
const upstreamHandoffSection = buildUpstreamHandoffSection(planTurn.upstreamHandoffs)
|
|
95
89
|
return (
|
|
96
90
|
mergeInstructionSections(
|
|
@@ -100,7 +94,7 @@ export function buildPlanTurnInstructionSections(planTurn: WorkstreamPlanTurnCon
|
|
|
100
94
|
)
|
|
101
95
|
}
|
|
102
96
|
|
|
103
|
-
export function buildPlanTurnSubmitToolDescription(planTurn:
|
|
97
|
+
export function buildPlanTurnSubmitToolDescription(planTurn: ThreadPlanTurnContext): string {
|
|
104
98
|
const requiredArtifacts =
|
|
105
99
|
planTurn.nodeSpec.deliverables
|
|
106
100
|
.filter((deliverable) => deliverable.required)
|
|
@@ -114,11 +108,11 @@ export function buildPlanTurnSubmitToolDescription(planTurn: WorkstreamPlanTurnC
|
|
|
114
108
|
`Structured output: ${
|
|
115
109
|
planTurn.nodeSpec.outputSchemaRef ? `must satisfy ${planTurn.nodeSpec.outputSchemaRef}` : 'optional'
|
|
116
110
|
}.`,
|
|
117
|
-
'Do not submit partial results.
|
|
111
|
+
'Do not submit partial results. notes is required — include a concise summary for downstream nodes.',
|
|
118
112
|
].join(' ')
|
|
119
113
|
}
|
|
120
114
|
|
|
121
|
-
export function buildPlanTurnPromptMessage(planTurn:
|
|
115
|
+
export function buildPlanTurnPromptMessage(planTurn: ThreadPlanTurnContext): ChatMessage {
|
|
122
116
|
return withMessageCreatedAt(
|
|
123
117
|
{
|
|
124
118
|
id: Bun.randomUUIDv7(),
|
|
@@ -8,13 +8,13 @@ import type {
|
|
|
8
8
|
LotaRuntimeWorkspaceProjectionState,
|
|
9
9
|
LotaRuntimeWorkspaceProvider,
|
|
10
10
|
} from './runtime-extensions'
|
|
11
|
-
import { asRecord, readInstructionSections, readOptionalString } from './
|
|
11
|
+
import { asRecord, readInstructionSections, readOptionalString } from './thread-chat-helpers'
|
|
12
12
|
|
|
13
13
|
function readOptionalBoolean(value: unknown): boolean | undefined {
|
|
14
14
|
return typeof value === 'boolean' ? value : undefined
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
interface
|
|
17
|
+
interface AssembledThreadTurnContext {
|
|
18
18
|
workspace: Record<string, unknown>
|
|
19
19
|
workspaceLifecycleState: LotaRuntimeWorkspaceLifecycleState | undefined
|
|
20
20
|
workspaceProfileState: LotaRuntimeWorkspaceProjectionState | undefined
|
|
@@ -28,9 +28,9 @@ interface AssembledWorkstreamTurnContext {
|
|
|
28
28
|
hookInstructionSections: string[]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export async function
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export async function assembleThreadTurnContext(params: {
|
|
32
|
+
thread: unknown
|
|
33
|
+
threadRef: RecordIdRef
|
|
34
34
|
orgRef: RecordIdRef
|
|
35
35
|
userRef: RecordIdRef
|
|
36
36
|
userName?: string | null
|
|
@@ -41,7 +41,7 @@ export async function assembleWorkstreamTurnContext(params: {
|
|
|
41
41
|
workspaceProvider?: LotaRuntimeWorkspaceProvider
|
|
42
42
|
turnHooks: LotaRuntimeTurnHooks
|
|
43
43
|
onStep?: (name: string) => void
|
|
44
|
-
}): Promise<
|
|
44
|
+
}): Promise<AssembledThreadTurnContext> {
|
|
45
45
|
const workspace = await params.workspacePromise
|
|
46
46
|
params.onStep?.('fetch-workspace')
|
|
47
47
|
|
|
@@ -104,8 +104,8 @@ export async function assembleWorkstreamTurnContext(params: {
|
|
|
104
104
|
|
|
105
105
|
const buildContextResult = asRecord(
|
|
106
106
|
await params.turnHooks.buildContext?.({
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
thread: params.thread,
|
|
108
|
+
threadRef: params.threadRef,
|
|
109
109
|
orgRef: params.orgRef,
|
|
110
110
|
userRef: params.userRef,
|
|
111
111
|
userName: params.userName,
|
|
@@ -144,8 +144,8 @@ export async function assembleWorkstreamTurnContext(params: {
|
|
|
144
144
|
|
|
145
145
|
const hookInstructionSections = readInstructionSections(
|
|
146
146
|
await params.turnHooks.buildExtraInstructionSections?.({
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
thread: params.thread,
|
|
148
|
+
threadRef: params.threadRef,
|
|
149
149
|
orgRef: params.orgRef,
|
|
150
150
|
userRef: params.userRef,
|
|
151
151
|
userName: params.userName,
|
|
@@ -16,8 +16,8 @@ import type {
|
|
|
16
16
|
import { agentRoster } from '../config/agent-defaults'
|
|
17
17
|
import { serverLogger } from '../config/logger'
|
|
18
18
|
import { executionPlanService } from './execution-plan.service'
|
|
19
|
-
import {
|
|
20
|
-
import type {
|
|
19
|
+
import { threadService } from './thread.service'
|
|
20
|
+
import type { NormalizedThread } from './thread.types'
|
|
21
21
|
|
|
22
22
|
const BOARD_COLUMN_ORDER = ['ready', 'running', 'awaiting-human', 'completed', 'blocked', 'failed'] as const
|
|
23
23
|
type BoardColumnStatus = (typeof BOARD_COLUMN_ORDER)[number]
|
|
@@ -33,12 +33,12 @@ const COLUMN_LABELS: Record<BoardColumnStatus, string> = {
|
|
|
33
33
|
|
|
34
34
|
type ActivePlanEntry = {
|
|
35
35
|
plan: SerializableExecutionPlan
|
|
36
|
-
|
|
36
|
+
thread: Pick<NormalizedThread, 'id' | 'title' | 'isRunning' | 'updatedAt'>
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
type AgentActivityDeps = {
|
|
40
|
-
executionPlanService: Pick<typeof executionPlanService, '
|
|
41
|
-
|
|
40
|
+
executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForThread'>
|
|
41
|
+
threadService: Pick<typeof threadService, 'listThreads'>
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function normalizeCardStatus(status: string): BoardColumnStatus {
|
|
@@ -97,8 +97,8 @@ function incrementCounts(counts: AgentActivityCounts, rawStatus: string): void {
|
|
|
97
97
|
export function planNodeToCard(
|
|
98
98
|
node: SerializablePlanNode,
|
|
99
99
|
plan: SerializableExecutionPlan,
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
threadId: string,
|
|
101
|
+
threadTitle: string,
|
|
102
102
|
): PlanNodeCard {
|
|
103
103
|
const approval = plan.approvals.find((candidate) => candidate.nodeId === node.id && candidate.status === 'pending')
|
|
104
104
|
|
|
@@ -111,8 +111,8 @@ export function planNodeToCard(
|
|
|
111
111
|
ownerRef: node.owner.ref,
|
|
112
112
|
planRunId: plan.runId,
|
|
113
113
|
planTitle: plan.title,
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
threadId,
|
|
115
|
+
threadTitle,
|
|
116
116
|
nodeType: node.type,
|
|
117
117
|
artifactCount: plan.artifacts.filter((artifact) => artifact.nodeId === node.id).length,
|
|
118
118
|
hasApproval: Boolean(approval),
|
|
@@ -129,11 +129,11 @@ export function planNodeToCard(
|
|
|
129
129
|
function buildPlanViewNode(
|
|
130
130
|
node: SerializablePlanNode,
|
|
131
131
|
plan: SerializableExecutionPlan,
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
threadId: string,
|
|
133
|
+
threadTitle: string,
|
|
134
134
|
): PlanViewNode {
|
|
135
135
|
return {
|
|
136
|
-
...planNodeToCard(node, plan,
|
|
136
|
+
...planNodeToCard(node, plan, threadId, threadTitle),
|
|
137
137
|
deliverableNames: node.deliverables.map((deliverable) => deliverable.name),
|
|
138
138
|
upstreamNodeIds: node.upstreamNodeIds,
|
|
139
139
|
downstreamNodeIds: node.downstreamNodeIds,
|
|
@@ -141,12 +141,12 @@ function buildPlanViewNode(
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export class AgentActivityService {
|
|
144
|
-
constructor(private readonly deps: AgentActivityDeps = { executionPlanService,
|
|
144
|
+
constructor(private readonly deps: AgentActivityDeps = { executionPlanService, threadService }) {}
|
|
145
145
|
|
|
146
146
|
async getBoard(userRef: string, orgRef: string): Promise<PlanBoardResponse> {
|
|
147
147
|
const activePlans = await this.getAllActivePlans(userRef, orgRef)
|
|
148
|
-
const cards = activePlans.flatMap(({ plan,
|
|
149
|
-
plan.nodes.map((node) => planNodeToCard(node, plan,
|
|
148
|
+
const cards = activePlans.flatMap(({ plan, thread }) =>
|
|
149
|
+
plan.nodes.map((node) => planNodeToCard(node, plan, thread.id, thread.title)),
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
const columns: PlanBoardColumn[] = BOARD_COLUMN_ORDER.map((status) => ({
|
|
@@ -171,7 +171,7 @@ export class AgentActivityService {
|
|
|
171
171
|
const match = activePlans.find(({ plan }) => plan.runId === planRunId)
|
|
172
172
|
if (!match) return null
|
|
173
173
|
|
|
174
|
-
const { plan,
|
|
174
|
+
const { plan, thread } = match
|
|
175
175
|
return {
|
|
176
176
|
planRunId: plan.runId,
|
|
177
177
|
title: plan.title,
|
|
@@ -179,7 +179,7 @@ export class AgentActivityService {
|
|
|
179
179
|
status: plan.status,
|
|
180
180
|
leadAgentId: plan.leadAgentId,
|
|
181
181
|
progress: { completed: plan.progress.completed + plan.progress.partial, total: plan.progress.total },
|
|
182
|
-
nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan,
|
|
182
|
+
nodes: plan.nodes.map((node) => buildPlanViewNode(node, plan, thread.id, thread.title)),
|
|
183
183
|
edges: plan.edges.map((edge) => ({ from: edge.source, to: edge.target })),
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -188,13 +188,13 @@ export class AgentActivityService {
|
|
|
188
188
|
const activePlans = await this.getAllActivePlans(userRef, orgRef)
|
|
189
189
|
const tasks: PlanNodeCard[] = []
|
|
190
190
|
|
|
191
|
-
for (const { plan,
|
|
191
|
+
for (const { plan, thread } of activePlans) {
|
|
192
192
|
for (const node of plan.nodes) {
|
|
193
193
|
const humanOwned = node.owner.executorType === 'user'
|
|
194
194
|
const awaitingHuman = node.status === 'awaiting-human'
|
|
195
195
|
if (!humanOwned && !awaitingHuman) continue
|
|
196
196
|
|
|
197
|
-
tasks.push(planNodeToCard(node, plan,
|
|
197
|
+
tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -209,7 +209,7 @@ export class AgentActivityService {
|
|
|
209
209
|
activityByAgent.set(agentId, this.createEmptyEntry(agentId))
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
for (const { plan,
|
|
212
|
+
for (const { plan, thread } of activePlans) {
|
|
213
213
|
const involvedAgents = new Set<string>()
|
|
214
214
|
|
|
215
215
|
for (const node of plan.nodes) {
|
|
@@ -221,7 +221,7 @@ export class AgentActivityService {
|
|
|
221
221
|
incrementCounts(entry.counts, node.status)
|
|
222
222
|
|
|
223
223
|
if (!isCompletedStatus(node.status)) {
|
|
224
|
-
entry.tasks.push(planNodeToCard(node, plan,
|
|
224
|
+
entry.tasks.push(planNodeToCard(node, plan, thread.id, thread.title))
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -234,15 +234,15 @@ export class AgentActivityService {
|
|
|
234
234
|
for (const agentId of involvedAgents) {
|
|
235
235
|
const entry = this.ensureEntry(activityByAgent, agentId)
|
|
236
236
|
this.ensureProjectEntry(entry.projects, {
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
threadId: thread.id,
|
|
238
|
+
threadTitle: thread.title,
|
|
239
239
|
planRunId: plan.runId,
|
|
240
240
|
planTitle: plan.title,
|
|
241
241
|
status: plan.status,
|
|
242
242
|
})
|
|
243
243
|
|
|
244
|
-
entry.isRunning = entry.isRunning ||
|
|
245
|
-
entry.lastActiveAt = maxIsoDate(entry.lastActiveAt,
|
|
244
|
+
entry.isRunning = entry.isRunning || thread.isRunning
|
|
245
|
+
entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, thread.updatedAt)
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -265,7 +265,7 @@ export class AgentActivityService {
|
|
|
265
265
|
pendingApprovalCount: userTasks.pendingApprovalCount,
|
|
266
266
|
awaitingHumanCount: userTasks.tasks.filter((task) => task.status === 'awaiting-human').length,
|
|
267
267
|
lastActiveAt: activePlans.reduce<string | null>(
|
|
268
|
-
(latest, entry) => maxIsoDate(latest, entry.
|
|
268
|
+
(latest, entry) => maxIsoDate(latest, entry.thread.updatedAt),
|
|
269
269
|
null,
|
|
270
270
|
),
|
|
271
271
|
},
|
|
@@ -274,14 +274,14 @@ export class AgentActivityService {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
async getAllActivePlans(userRef: string, orgRef: string): Promise<ActivePlanEntry[]> {
|
|
277
|
-
const
|
|
277
|
+
const threads = await this.listRelevantThreads(userRef, orgRef)
|
|
278
278
|
const planResults = await Promise.all(
|
|
279
|
-
|
|
279
|
+
threads.map(async (thread) => {
|
|
280
280
|
try {
|
|
281
|
-
const plans = await this.deps.executionPlanService.
|
|
282
|
-
return plans.map((plan) => ({ plan,
|
|
281
|
+
const plans = await this.deps.executionPlanService.getActivePlansForThread(thread.id)
|
|
282
|
+
return plans.map((plan) => ({ plan, thread }))
|
|
283
283
|
} catch (error) {
|
|
284
|
-
serverLogger.error`Failed to load active plans for
|
|
284
|
+
serverLogger.error`Failed to load active plans for thread ${thread.id}: ${error}`
|
|
285
285
|
return []
|
|
286
286
|
}
|
|
287
287
|
}),
|
|
@@ -290,26 +290,21 @@ export class AgentActivityService {
|
|
|
290
290
|
return planResults.flat()
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
private async
|
|
293
|
+
private async listRelevantThreads(userRef: string, orgRef: string): Promise<NormalizedThread[]> {
|
|
294
294
|
const [direct, core, group] = await Promise.all([
|
|
295
|
-
this.deps.
|
|
296
|
-
this.deps.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
includeArchived: false,
|
|
300
|
-
}),
|
|
301
|
-
this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
|
|
302
|
-
mode: 'group',
|
|
303
|
-
core: false,
|
|
295
|
+
this.deps.threadService.listThreads(userRef, orgRef, { type: 'default', includeArchived: false }),
|
|
296
|
+
this.deps.threadService.listThreads(userRef, orgRef, { type: 'thread', includeArchived: false }),
|
|
297
|
+
this.deps.threadService.listThreads(userRef, orgRef, {
|
|
298
|
+
type: 'group',
|
|
304
299
|
includeArchived: false,
|
|
305
300
|
take: 500,
|
|
306
301
|
page: 1,
|
|
307
302
|
}),
|
|
308
303
|
])
|
|
309
304
|
|
|
310
|
-
const deduped = new Map<string,
|
|
311
|
-
for (const
|
|
312
|
-
deduped.set(
|
|
305
|
+
const deduped = new Map<string, NormalizedThread>()
|
|
306
|
+
for (const thread of [...direct.threads, ...core.threads, ...group.threads]) {
|
|
307
|
+
deduped.set(thread.id, thread)
|
|
313
308
|
}
|
|
314
309
|
|
|
315
310
|
return [...deduped.values()]
|
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
25
25
|
import { buildIndexedRepositoriesContext, getPluginService } from '../runtime/plugin-resolution'
|
|
26
26
|
import { getTurnHooks } from '../runtime/runtime-extensions'
|
|
27
|
-
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/
|
|
27
|
+
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
|
|
28
28
|
import { nodeWorkspaceService } from './node-workspace.service'
|
|
29
29
|
import type { PlanValidationIssueInput } from './plan-validator.service'
|
|
30
|
-
import {
|
|
30
|
+
import { ThreadSchema } from './thread.types'
|
|
31
31
|
import { writeIntentValidatorService } from './write-intent-validator.service'
|
|
32
32
|
|
|
33
33
|
function applyToolPolicy(tools: ToolSet, nodeSpec: PlanNodeSpec): ToolSet {
|
|
@@ -108,20 +108,20 @@ class AgentExecutorService {
|
|
|
108
108
|
throw new Error(`Agent executor "${agentId}" is not registered.`)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const
|
|
112
|
-
TABLES.
|
|
113
|
-
{ id: ensureRecordId(params.context.
|
|
114
|
-
|
|
111
|
+
const thread = await databaseService.findOne(
|
|
112
|
+
TABLES.THREAD,
|
|
113
|
+
{ id: ensureRecordId(params.context.threadId, TABLES.THREAD) },
|
|
114
|
+
ThreadSchema,
|
|
115
115
|
)
|
|
116
|
-
if (!
|
|
117
|
-
throw new Error(`
|
|
116
|
+
if (!thread) {
|
|
117
|
+
throw new Error(`Thread ${params.context.threadId} not found for dispatched execution.`)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const organizationRef = ensureRecordId(params.context.organizationId, TABLES.ORGANIZATION)
|
|
121
|
-
const
|
|
122
|
-
const userRefSource = params.context.userId ??
|
|
121
|
+
const threadRef = ensureRecordId(params.context.threadId, TABLES.THREAD)
|
|
122
|
+
const userRefSource = params.context.userId ?? thread.userId
|
|
123
123
|
if (!userRefSource) {
|
|
124
|
-
throw new Error(`
|
|
124
|
+
throw new Error(`Thread ${params.context.threadId} is missing a user context for dispatched execution.`)
|
|
125
125
|
}
|
|
126
126
|
const userRef = ensureRecordId(userRefSource, TABLES.USER)
|
|
127
127
|
const userName = params.context.userName ?? 'User'
|
|
@@ -147,7 +147,7 @@ class AgentExecutorService {
|
|
|
147
147
|
|
|
148
148
|
const mode = params.executionMode ?? 'linear'
|
|
149
149
|
|
|
150
|
-
const dispatchMode =
|
|
150
|
+
const dispatchMode = thread.type === 'group' ? 'fixedThreadMode' : 'direct'
|
|
151
151
|
const dispatchInstructionSections = [
|
|
152
152
|
buildOwnershipDispatchContextSection({
|
|
153
153
|
node: params.nodeSpec,
|
|
@@ -161,8 +161,8 @@ class AgentExecutorService {
|
|
|
161
161
|
await turnHooks.resolveAgent?.({
|
|
162
162
|
agentId,
|
|
163
163
|
mode: dispatchMode,
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
thread,
|
|
165
|
+
threadRef,
|
|
166
166
|
orgRef: organizationRef,
|
|
167
167
|
userRef,
|
|
168
168
|
userName,
|
|
@@ -176,7 +176,7 @@ class AgentExecutorService {
|
|
|
176
176
|
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
177
177
|
const runtimeConfig = getAgentRuntimeConfig({
|
|
178
178
|
agentId: resolvedAgentId,
|
|
179
|
-
|
|
179
|
+
threadType: thread.type,
|
|
180
180
|
mode: dispatchMode,
|
|
181
181
|
onboardingActive: false,
|
|
182
182
|
linearInstalled: Boolean(linearInstallation),
|
|
@@ -192,9 +192,9 @@ class AgentExecutorService {
|
|
|
192
192
|
orgId: organizationRef,
|
|
193
193
|
userId: userRef,
|
|
194
194
|
userName,
|
|
195
|
-
|
|
195
|
+
threadId: threadRef,
|
|
196
196
|
orgIdString: params.context.organizationId,
|
|
197
|
-
|
|
197
|
+
threadType: thread.type,
|
|
198
198
|
mode: dispatchMode,
|
|
199
199
|
linearInstalled: Boolean(linearInstallation),
|
|
200
200
|
onboardingActive: false,
|
|
@@ -302,7 +302,6 @@ class AgentExecutorService {
|
|
|
302
302
|
structuredOutput: finalResult.structuredOutput,
|
|
303
303
|
artifacts: finalResult.artifacts,
|
|
304
304
|
notes: 'Execution incomplete: missing required deliverables or validation failures.',
|
|
305
|
-
quality: 'partial',
|
|
306
305
|
}
|
|
307
306
|
nodeWorkspaceService.cleanup(workspace)
|
|
308
307
|
return result
|
|
@@ -312,7 +311,6 @@ class AgentExecutorService {
|
|
|
312
311
|
structuredOutput: finalResult.structuredOutput,
|
|
313
312
|
artifacts: finalResult.artifacts,
|
|
314
313
|
notes: finalResult.notes,
|
|
315
|
-
quality: finalResult.quality,
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
nodeWorkspaceService.cleanup(workspace)
|
|
@@ -2,7 +2,7 @@ import type { RecordIdRef } from '../db/record-id'
|
|
|
2
2
|
import { recordIdToString } from '../db/record-id'
|
|
3
3
|
import { TABLES } from '../db/tables'
|
|
4
4
|
import { attachmentStorageService } from '../storage/attachment-storage.service'
|
|
5
|
-
import type {
|
|
5
|
+
import type { UploadedThreadAttachment as SdkUploadedThreadAttachment } from '../storage/attachment-storage.service'
|
|
6
6
|
import type { MessagePartLike, ReadableUploadMetadata as SdkReadableUploadMetadata } from '../storage/attachment-types'
|
|
7
7
|
|
|
8
8
|
export type ReadableUploadMetadata = SdkReadableUploadMetadata
|
|
@@ -134,7 +134,7 @@ class AttachmentService {
|
|
|
134
134
|
return attachmentStorageService.uploadOrganizationDocument({ file, orgId: toOrgId(orgId), namespace, relativePath })
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
async
|
|
137
|
+
async uploadThreadAttachment({
|
|
138
138
|
file,
|
|
139
139
|
orgId,
|
|
140
140
|
userId,
|
|
@@ -142,12 +142,8 @@ class AttachmentService {
|
|
|
142
142
|
file: File
|
|
143
143
|
orgId: RecordIdRef
|
|
144
144
|
userId: RecordIdRef
|
|
145
|
-
}): Promise<
|
|
146
|
-
return attachmentStorageService.
|
|
147
|
-
file,
|
|
148
|
-
orgId: toOrgId(orgId),
|
|
149
|
-
userId: toUserId(userId),
|
|
150
|
-
})
|
|
145
|
+
}): Promise<SdkUploadedThreadAttachment> {
|
|
146
|
+
return attachmentStorageService.uploadThreadAttachment({ file, orgId: toOrgId(orgId), userId: toUserId(userId) })
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
|