@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.
Files changed (106) hide show
  1. package/infrastructure/schema/00_identity.surql +2 -2
  2. package/infrastructure/schema/00_thread.surql +73 -0
  3. package/infrastructure/schema/02_execution_plan.surql +10 -11
  4. package/infrastructure/schema/04_runtime_bootstrap.surql +1 -0
  5. package/infrastructure/schema/10_autonomous_job.surql +3 -3
  6. package/package.json +2 -2
  7. package/src/ai/definitions.ts +1 -1
  8. package/src/config/agent-defaults.ts +5 -5
  9. package/src/config/index.ts +1 -1
  10. package/src/config/thread-defaults.ts +72 -0
  11. package/src/create-runtime.ts +90 -94
  12. package/src/db/record-id.ts +21 -21
  13. package/src/db/service.ts +44 -40
  14. package/src/db/tables.ts +3 -3
  15. package/src/db/{workstream-message-row.ts → thread-message-row.ts} +3 -3
  16. package/src/queues/context-compaction.queue.ts +6 -6
  17. package/src/queues/plan-agent-heartbeat.queue.ts +3 -3
  18. package/src/queues/post-chat-memory.queue.ts +1 -1
  19. package/src/queues/title-generation.queue.ts +10 -13
  20. package/src/redis/index.ts +1 -1
  21. package/src/redis/stream-context.ts +1 -1
  22. package/src/runtime/agent-identity-overrides.ts +1 -1
  23. package/src/runtime/agent-runtime-policy.ts +19 -21
  24. package/src/runtime/chat-request-routing.ts +1 -1
  25. package/src/runtime/context-compaction-constants.ts +1 -1
  26. package/src/runtime/context-compaction.ts +1 -1
  27. package/src/runtime/execution-plan.ts +1 -1
  28. package/src/runtime/index.ts +1 -1
  29. package/src/runtime/memory-digest-policy.ts +1 -1
  30. package/src/runtime/plugin-types.ts +1 -1
  31. package/src/runtime/post-turn-side-effects.ts +35 -35
  32. package/src/runtime/runtime-config.ts +24 -21
  33. package/src/runtime/runtime-extensions.ts +11 -11
  34. package/src/runtime/social-chat-agent-runner.ts +3 -3
  35. package/src/runtime/social-chat-history.ts +1 -1
  36. package/src/runtime/social-chat.ts +6 -6
  37. package/src/runtime/team-consultation-orchestrator.ts +1 -1
  38. package/src/runtime/{workstream-chat-helpers.ts → thread-chat-helpers.ts} +7 -7
  39. package/src/runtime/{workstream-plan-turn.ts → thread-plan-turn.ts} +11 -17
  40. package/src/runtime/{workstream-turn-context.ts → thread-turn-context.ts} +10 -10
  41. package/src/services/agent-activity.service.ts +39 -44
  42. package/src/services/agent-executor.service.ts +17 -19
  43. package/src/services/attachment.service.ts +4 -8
  44. package/src/services/autonomous-job.service.ts +29 -28
  45. package/src/services/context-compaction.service.ts +19 -29
  46. package/src/services/execution-plan.service.ts +58 -70
  47. package/src/services/global-orchestrator.service.ts +5 -5
  48. package/src/services/index.ts +6 -6
  49. package/src/services/memory.service.ts +1 -1
  50. package/src/services/monitoring-window.service.ts +2 -2
  51. package/src/services/mutating-approval.service.ts +7 -10
  52. package/src/services/node-workspace.service.ts +8 -7
  53. package/src/services/notification.service.ts +1 -1
  54. package/src/services/organization.service.ts +9 -9
  55. package/src/services/ownership-dispatcher.service.ts +13 -19
  56. package/src/services/plan-agent-heartbeat.service.ts +13 -13
  57. package/src/services/plan-agent-query.service.ts +7 -7
  58. package/src/services/plan-artifact.service.ts +1 -2
  59. package/src/services/plan-coordination.service.ts +4 -4
  60. package/src/services/plan-cycle.service.ts +7 -7
  61. package/src/services/plan-deadline.service.ts +4 -4
  62. package/src/services/plan-event-delivery.service.ts +8 -12
  63. package/src/services/plan-executor.service.ts +25 -39
  64. package/src/services/plan-run-data.ts +27 -8
  65. package/src/services/plan-run.service.ts +7 -9
  66. package/src/services/plan-scheduler.service.ts +4 -4
  67. package/src/services/plan-template.service.ts +2 -2
  68. package/src/services/plan-validator.service.ts +0 -11
  69. package/src/services/plugin-executor.service.ts +1 -1
  70. package/src/services/queue-job.service.ts +1 -1
  71. package/src/services/recent-activity-title.service.ts +1 -1
  72. package/src/services/recent-activity.service.ts +4 -4
  73. package/src/services/system-executor.service.ts +2 -2
  74. package/src/services/{workstream-message.service.ts → thread-message.service.ts} +72 -76
  75. package/src/services/thread-plan-registry.service.ts +22 -0
  76. package/src/services/thread-title.service.ts +39 -0
  77. package/src/services/{workstream-turn-preparation.service.ts → thread-turn-preparation.service.ts} +148 -171
  78. package/src/services/{workstream-turn.ts → thread-turn.ts} +27 -31
  79. package/src/services/thread.service.ts +853 -0
  80. package/src/services/thread.types.ts +17 -0
  81. package/src/storage/attachment-storage.service.ts +4 -4
  82. package/src/system-agents/index.ts +1 -1
  83. package/src/system-agents/memory.agent.ts +1 -1
  84. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  85. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  86. package/src/system-agents/researcher.agent.ts +3 -3
  87. package/src/system-agents/{workstream-router.agent.ts → thread-router.agent.ts} +68 -135
  88. package/src/system-agents/title-generator.agent.ts +8 -8
  89. package/src/tools/execution-plan.tool.ts +39 -40
  90. package/src/tools/memory-block.tool.ts +4 -4
  91. package/src/tools/research-topic.tool.ts +1 -0
  92. package/src/tools/search-web.tool.ts +1 -1
  93. package/src/tools/search.tool.ts +4 -4
  94. package/src/tools/team-think.tool.ts +9 -9
  95. package/src/utils/async.ts +6 -7
  96. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  97. package/src/workers/regular-chat-memory-digest.runner.ts +43 -43
  98. package/src/workers/skill-extraction.runner.ts +9 -13
  99. package/src/workers/utils/{workstream-message-query.ts → thread-message-query.ts} +21 -21
  100. package/infrastructure/schema/00_workstream.surql +0 -64
  101. package/src/config/workstream-defaults.ts +0 -72
  102. package/src/services/workstream-plan-registry.service.ts +0 -22
  103. package/src/services/workstream-title.service.ts +0 -42
  104. package/src/services/workstream.service.ts +0 -803
  105. package/src/services/workstream.types.ts +0 -17
  106. /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 './workstream-chat-helpers'
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: 'fixedWorkstreamMode',
366
- workstreamMode: 'group',
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: 'workstreamMode',
411
- workstreamMode: 'group',
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
- workstreamId: `social:slack:${messageContext.threadId}`,
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 './workstream-chat-helpers'
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 WorkstreamHistoryMessage {
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): WorkstreamHistoryMessage[] {
45
+ export function toHistoryMessages(messages: ChatMessageLike[], maxItems = 24): ThreadHistoryMessage[] {
46
46
  return messages
47
- .map((message): WorkstreamHistoryMessage | null => {
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 WorkstreamHistoryMessage => message !== null)
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 appendPersistedWorkstreamContextToHistoryMessages(
90
- historyMessages: WorkstreamHistoryMessage[],
89
+ export function appendPersistedThreadContextToHistoryMessages(
90
+ historyMessages: ThreadHistoryMessage[],
91
91
  params: { compactionSummary?: string | null },
92
- ): WorkstreamHistoryMessage[] {
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
- handoffContext: PlanNodeHandoffContext
12
+ summary?: string
19
13
  }
20
14
 
21
- export interface WorkstreamPlanTurnContext {
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: WorkstreamPlanTurnContext): string {
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 handoff context.',
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
- 'Include notes with a concise completion summary. Include handoffContext with summary, key decisions, open questions, risks, and recommendations for downstream nodes.',
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
- handoffContext: handoff.handoffContext,
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: WorkstreamPlanTurnContext): string[] {
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: WorkstreamPlanTurnContext): string {
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. Include durable handoffContext for downstream nodes.',
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: WorkstreamPlanTurnContext): ChatMessage {
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 './workstream-chat-helpers'
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 AssembledWorkstreamTurnContext {
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 assembleWorkstreamTurnContext(params: {
32
- workstream: unknown
33
- workstreamRef: RecordIdRef
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<AssembledWorkstreamTurnContext> {
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
- workstream: params.workstream,
108
- workstreamRef: params.workstreamRef,
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
- workstream: params.workstream,
148
- workstreamRef: params.workstreamRef,
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 { workstreamService } from './workstream.service'
20
- import type { NormalizedWorkstream } from './workstream.types'
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
- workstream: Pick<NormalizedWorkstream, 'id' | 'title' | 'isRunning' | 'updatedAt'>
36
+ thread: Pick<NormalizedThread, 'id' | 'title' | 'isRunning' | 'updatedAt'>
37
37
  }
38
38
 
39
39
  type AgentActivityDeps = {
40
- executionPlanService: Pick<typeof executionPlanService, 'getActivePlansForWorkstream'>
41
- workstreamService: Pick<typeof workstreamService, 'listWorkstreams'>
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
- workstreamId: string,
101
- workstreamTitle: string,
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
- workstreamId,
115
- workstreamTitle,
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
- workstreamId: string,
133
- workstreamTitle: string,
132
+ threadId: string,
133
+ threadTitle: string,
134
134
  ): PlanViewNode {
135
135
  return {
136
- ...planNodeToCard(node, plan, workstreamId, workstreamTitle),
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, workstreamService }) {}
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, workstream }) =>
149
- plan.nodes.map((node) => planNodeToCard(node, plan, workstream.id, workstream.title)),
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, workstream } = match
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, workstream.id, workstream.title)),
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, workstream } of activePlans) {
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, workstream.id, workstream.title))
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, workstream } of activePlans) {
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, workstream.id, workstream.title))
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
- workstreamId: workstream.id,
238
- workstreamTitle: workstream.title,
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 || workstream.isRunning
245
- entry.lastActiveAt = maxIsoDate(entry.lastActiveAt, workstream.updatedAt)
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.workstream.updatedAt),
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 workstreams = await this.listRelevantWorkstreams(userRef, orgRef)
277
+ const threads = await this.listRelevantThreads(userRef, orgRef)
278
278
  const planResults = await Promise.all(
279
- workstreams.map(async (workstream) => {
279
+ threads.map(async (thread) => {
280
280
  try {
281
- const plans = await this.deps.executionPlanService.getActivePlansForWorkstream(workstream.id)
282
- return plans.map((plan) => ({ plan, workstream }))
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 workstream ${workstream.id}: ${error}`
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 listRelevantWorkstreams(userRef: string, orgRef: string): Promise<NormalizedWorkstream[]> {
293
+ private async listRelevantThreads(userRef: string, orgRef: string): Promise<NormalizedThread[]> {
294
294
  const [direct, core, group] = await Promise.all([
295
- this.deps.workstreamService.listWorkstreams(userRef, orgRef, { mode: 'direct', includeArchived: false }),
296
- this.deps.workstreamService.listWorkstreams(userRef, orgRef, {
297
- mode: 'group',
298
- core: true,
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, NormalizedWorkstream>()
311
- for (const workstream of [...direct.workstreams, ...core.workstreams, ...group.workstreams]) {
312
- deduped.set(workstream.id, workstream)
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/workstream-chat-helpers'
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 { WorkstreamSchema } from './workstream.types'
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 workstream = await databaseService.findOne(
112
- TABLES.WORKSTREAM,
113
- { id: ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM) },
114
- WorkstreamSchema,
111
+ const thread = await databaseService.findOne(
112
+ TABLES.THREAD,
113
+ { id: ensureRecordId(params.context.threadId, TABLES.THREAD) },
114
+ ThreadSchema,
115
115
  )
116
- if (!workstream) {
117
- throw new Error(`Workstream ${params.context.workstreamId} not found for dispatched execution.`)
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 workstreamRef = ensureRecordId(params.context.workstreamId, TABLES.WORKSTREAM)
122
- const userRefSource = params.context.userId ?? workstream.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(`Workstream ${params.context.workstreamId} is missing a user context for dispatched execution.`)
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 = workstream.mode === 'group' ? 'fixedWorkstreamMode' : 'direct'
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
- workstream,
165
- workstreamRef,
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
- workstreamMode: workstream.mode,
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
- workstreamId: workstreamRef,
195
+ threadId: threadRef,
196
196
  orgIdString: params.context.organizationId,
197
- workstreamMode: workstream.mode,
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 { UploadedWorkstreamAttachment as SdkUploadedWorkstreamAttachment } from '../storage/attachment-storage.service'
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 uploadWorkstreamAttachment({
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<SdkUploadedWorkstreamAttachment> {
146
- return attachmentStorageService.uploadWorkstreamAttachment({
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