@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
@@ -30,15 +30,15 @@ import type { RecordIdInput } from '../db/record-id'
30
30
  import { databaseService } from '../db/service'
31
31
  import { TABLES } from '../db/tables'
32
32
  import type { AutonomousJobQueuePayload } from '../queues/autonomous-job.queue'
33
- import { extractMessageText } from '../runtime/workstream-chat-helpers'
33
+ import { extractMessageText } from '../runtime/thread-chat-helpers'
34
34
  import { buildAutonomousAtJobId, encodeBullmqId } from '../utils/autonomous-job-ids'
35
35
  import { toIsoDateTimeString, toOptionalIsoDateTimeString } from '../utils/date-time'
36
36
  import { compactRecord, compactWhitespace, stringifyUnknown, truncateText } from '../utils/string'
37
37
  import { executionPlanService } from './execution-plan.service'
38
38
  import { getNotificationService } from './notification.service'
39
39
  import { queueJobService } from './queue-job.service'
40
- import { runWorkstreamTurnInBackground } from './workstream-turn'
41
- import { workstreamService } from './workstream.service'
40
+ import { runThreadTurnInBackground } from './thread-turn'
41
+ import { threadService } from './thread.service'
42
42
 
43
43
  const AUTONOMOUS_JOB_QUEUE_NAME = 'autonomous-job'
44
44
 
@@ -51,7 +51,7 @@ const AutonomousJobRowSchema = z.object({
51
51
  organizationId: recordIdSchema,
52
52
  ownerUserId: recordIdSchema,
53
53
  ownerUserName: z.string().optional(),
54
- workstreamId: recordIdSchema,
54
+ threadId: recordIdSchema,
55
55
  agentId: z.string(),
56
56
  title: z.string(),
57
57
  prompt: z.string(),
@@ -72,7 +72,7 @@ const AutonomousJobRowSchema = z.object({
72
72
  const AutonomousJobRunRowSchema = z.object({
73
73
  id: recordIdSchema,
74
74
  autonomousJobId: recordIdSchema,
75
- workstreamId: recordIdSchema,
75
+ threadId: recordIdSchema,
76
76
  queueJobId: recordIdSchema.optional(),
77
77
  status: AutonomousJobRunStatusSchema,
78
78
  inputMessageId: z.string().optional(),
@@ -111,7 +111,7 @@ class AutonomousJobService {
111
111
  organizationId: recordIdToString(row.organizationId, TABLES.ORGANIZATION),
112
112
  ownerUserId: recordIdToString(row.ownerUserId, TABLES.USER),
113
113
  ownerUserName: row.ownerUserName,
114
- workstreamId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
114
+ threadId: recordIdToString(row.threadId, TABLES.THREAD),
115
115
  agentId: row.agentId,
116
116
  title: row.title,
117
117
  prompt: row.prompt,
@@ -134,7 +134,7 @@ class AutonomousJobService {
134
134
  return AutonomousJobRunSchema.parse({
135
135
  id: recordIdToString(row.id, TABLES.AUTONOMOUS_JOB_RUN),
136
136
  autonomousJobId: recordIdToString(row.autonomousJobId, TABLES.AUTONOMOUS_JOB),
137
- workstreamId: recordIdToString(row.workstreamId, TABLES.WORKSTREAM),
137
+ threadId: recordIdToString(row.threadId, TABLES.THREAD),
138
138
  queueJobId: row.queueJobId ? recordIdToString(row.queueJobId, TABLES.QUEUE_JOB) : undefined,
139
139
  status: row.status,
140
140
  inputMessageId: row.inputMessageId,
@@ -175,7 +175,7 @@ class AutonomousJobService {
175
175
  kind: 'notify',
176
176
  params: {
177
177
  organizationId: string
178
- workstreamId: string
178
+ threadId: string
179
179
  title: string
180
180
  body: string
181
181
  severity: 'info' | 'warning'
@@ -204,7 +204,7 @@ class AutonomousJobService {
204
204
 
205
205
  private async createRunRow(params: {
206
206
  autonomousJobId: RecordIdInput
207
- workstreamId: RecordIdInput
207
+ threadId: RecordIdInput
208
208
  queueJobId?: RecordIdInput
209
209
  status?: AutonomousJobRunStatus
210
210
  }): Promise<AutonomousJobRunRow> {
@@ -214,7 +214,7 @@ class AutonomousJobService {
214
214
  runId,
215
215
  compactRecord({
216
216
  autonomousJobId: ensureRecordId(params.autonomousJobId, TABLES.AUTONOMOUS_JOB),
217
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
217
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
218
218
  queueJobId: params.queueJobId ? ensureRecordId(params.queueJobId, TABLES.QUEUE_JOB) : undefined,
219
219
  status: params.status ?? 'queued',
220
220
  assistantMessageIds: [],
@@ -293,8 +293,8 @@ class AutonomousJobService {
293
293
  if (row.schedule.kind === 'at') {
294
294
  const queuedRun = options.reusePendingAtRun
295
295
  ? ((await this.findRecoverableRunRow(row.id)) ??
296
- (await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })))
297
- : await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })
296
+ (await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })))
297
+ : await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
298
298
  const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
299
299
  const enqueueResult = await enqueueAutonomousJobRun({
300
300
  payload: {
@@ -334,10 +334,11 @@ class AutonomousJobService {
334
334
  const parsed = CreateAutonomousJobInputSchema.parse(input)
335
335
  const organizationId = ensureRecordId(parsed.organizationId, TABLES.ORGANIZATION)
336
336
  const ownerUserId = ensureRecordId(parsed.ownerUserId, TABLES.USER)
337
- const workstream = await workstreamService.createWorkstream(ownerUserId, organizationId, {
338
- mode: 'group',
339
- core: false,
340
- title: parsed.workstreamTitle ?? parsed.title,
337
+ const thread = await threadService.createThread({
338
+ userId: ownerUserId,
339
+ organizationId,
340
+ type: 'group',
341
+ title: parsed.threadTitle ?? parsed.title,
341
342
  })
342
343
  const jobId = new RecordId(TABLES.AUTONOMOUS_JOB, Bun.randomUUIDv7())
343
344
  const nextRunAt = this.computeNextRunAt(parsed.schedule)
@@ -348,7 +349,7 @@ class AutonomousJobService {
348
349
  organizationId,
349
350
  ownerUserId,
350
351
  ownerUserName: parsed.ownerUserName,
351
- workstreamId: ensureRecordId(workstream.id, TABLES.WORKSTREAM),
352
+ threadId: ensureRecordId(thread.id, TABLES.THREAD),
352
353
  agentId: parsed.agentId,
353
354
  title: parsed.title,
354
355
  prompt: parsed.prompt,
@@ -409,7 +410,7 @@ class AutonomousJobService {
409
410
  await this.unscheduleRow(existing)
410
411
 
411
412
  if (parsed.title && compactWhitespace(parsed.title) !== compactWhitespace(existing.title)) {
412
- await workstreamService.updateTitle(existing.workstreamId, parsed.title)
413
+ await threadService.updateTitle(existing.threadId, parsed.title)
413
414
  }
414
415
 
415
416
  const nextRunAt = this.computeNextRunAt(parsed.schedule ?? existing.schedule)
@@ -465,7 +466,7 @@ class AutonomousJobService {
465
466
 
466
467
  async runNow(jobId: RecordIdInput): Promise<AutonomousJobRun> {
467
468
  const row = await this.getRow(jobId)
468
- const queuedRun = await this.createRunRow({ autonomousJobId: row.id, workstreamId: row.workstreamId })
469
+ const queuedRun = await this.createRunRow({ autonomousJobId: row.id, threadId: row.threadId })
469
470
  const { enqueueAutonomousJobRun } = await import('../queues/autonomous-job.queue')
470
471
  const enqueueResult = await enqueueAutonomousJobRun({
471
472
  payload: {
@@ -501,7 +502,7 @@ class AutonomousJobService {
501
502
  async delete(jobId: RecordIdInput): Promise<AutonomousJob> {
502
503
  const row = await this.getRow(jobId)
503
504
  const cancelled = await this.cancel(row.id)
504
- await workstreamService.updateStatus(row.workstreamId, 'archived')
505
+ await threadService.updateStatus(row.threadId, 'archived')
505
506
  return cancelled
506
507
  }
507
508
 
@@ -526,7 +527,7 @@ class AutonomousJobService {
526
527
  ? await this.getRunRow(job.data.autonomousJobRunId)
527
528
  : await this.createRunRow({
528
529
  autonomousJobId: autonomousJobRow.id,
529
- workstreamId: autonomousJobRow.workstreamId,
530
+ threadId: autonomousJobRow.threadId,
530
531
  queueJobId,
531
532
  status: 'queued',
532
533
  })
@@ -546,18 +547,18 @@ class AutonomousJobService {
546
547
  )) ?? runRow
547
548
 
548
549
  try {
549
- const workstream = await workstreamService.getWorkstream(autonomousJobRow.workstreamId)
550
+ const thread = await threadService.getThread(autonomousJobRow.threadId)
550
551
  const inputMessage = this.buildSyntheticUserMessage(autonomousJobRow.prompt)
551
- const turnResult = await runWorkstreamTurnInBackground({
552
- workstream,
553
- workstreamRef: ensureRecordId(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
552
+ const turnResult = await runThreadTurnInBackground({
553
+ thread,
554
+ threadRef: ensureRecordId(autonomousJobRow.threadId, TABLES.THREAD),
554
555
  orgRef: ensureRecordId(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
555
556
  userRef: ensureRecordId(autonomousJobRow.ownerUserId, TABLES.USER),
556
557
  userName: autonomousJobRow.ownerUserName,
557
558
  agentIdOverride: autonomousJobRow.agentId,
558
559
  inputMessage,
559
560
  })
560
- const activePlan = await executionPlanService.getActivePlanForWorkstream(autonomousJobRow.workstreamId)
561
+ const activePlan = await executionPlanService.getActivePlanForThread(autonomousJobRow.threadId)
561
562
  const runStatus: AutonomousJobRunStatus = activePlan?.status === 'awaiting-human' ? 'awaiting-human' : 'completed'
562
563
  const summary = truncateText(
563
564
  turnResult.assistantMessages
@@ -611,7 +612,7 @@ class AutonomousJobService {
611
612
 
612
613
  await this.maybeNotify('notify', {
613
614
  organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
614
- workstreamId: recordIdToString(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
615
+ threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
615
616
  severity: 'info',
616
617
  title: `${autonomousJobRow.title} completed`,
617
618
  body: summary,
@@ -664,7 +665,7 @@ class AutonomousJobService {
664
665
 
665
666
  await this.maybeNotify('notify', {
666
667
  organizationId: recordIdToString(autonomousJobRow.organizationId, TABLES.ORGANIZATION),
667
- workstreamId: recordIdToString(autonomousJobRow.workstreamId, TABLES.WORKSTREAM),
668
+ threadId: recordIdToString(autonomousJobRow.threadId, TABLES.THREAD),
668
669
  severity: 'warning',
669
670
  title: autoPause
670
671
  ? `${autonomousJobRow.title} paused after repeated failures`
@@ -7,13 +7,13 @@ import { databaseService } from '../db/service'
7
7
  import { TABLES } from '../db/tables'
8
8
  import { getRedisConnection } from '../redis/connection-accessor'
9
9
  import { withRedisLeaseLock } from '../redis/redis-lease-lock'
10
- import { CONTEXT_WINDOW_TOKENS, WORKSTREAM_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
10
+ import { CONTEXT_WINDOW_TOKENS, THREAD_RAW_TAIL_MESSAGES } from '../runtime/context-compaction-constants'
11
11
  import { contextCompactionRuntime, compactMemoryBlockSummary } from './context-compaction-runtime.singleton'
12
- import { workstreamMessageService } from './workstream-message.service'
13
- import { WorkstreamSchema } from './workstream.types'
12
+ import { threadMessageService } from './thread-message.service'
13
+ import { ThreadSchema } from './thread.types'
14
14
 
15
15
  interface PersistedCompactionMetrics {
16
- domain: 'workstream'
16
+ domain: 'thread'
17
17
  entityId: string
18
18
  inputChars: number
19
19
  outputChars: number
@@ -37,11 +37,8 @@ class ContextCompactionService {
37
37
  return contextCompactionRuntime.shouldCompactHistory(params)
38
38
  }
39
39
 
40
- async compactWorkstreamHistory(params: {
41
- workstreamId: RecordIdRef
42
- contextSize?: number
43
- }): Promise<{ compacted: boolean }> {
44
- const entityId = recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
40
+ async compactThreadHistory(params: { threadId: RecordIdRef; contextSize?: number }): Promise<{ compacted: boolean }> {
41
+ const entityId = recordIdToString(params.threadId, TABLES.THREAD)
45
42
 
46
43
  return withRedisLeaseLock(
47
44
  {
@@ -52,24 +49,20 @@ class ContextCompactionService {
52
49
  label: 'context-compaction',
53
50
  },
54
51
  async () => {
55
- const workstream = await databaseService.findOne(
56
- TABLES.WORKSTREAM,
57
- { id: params.workstreamId },
58
- WorkstreamSchema,
59
- )
60
- if (!workstream) {
61
- throw new Error(`Workstream not found for compaction: ${entityId}`)
52
+ const thread = await databaseService.findOne(TABLES.THREAD, { id: params.threadId }, ThreadSchema)
53
+ if (!thread) {
54
+ throw new Error(`Thread not found for compaction: ${entityId}`)
62
55
  }
63
56
 
64
- const liveMessages = await workstreamMessageService.listMessagesAfterCursor(
65
- params.workstreamId,
66
- typeof workstream.lastCompactedMessageId === 'string' ? workstream.lastCompactedMessageId : undefined,
57
+ const liveMessages = await threadMessageService.listMessagesAfterCursor(
58
+ params.threadId,
59
+ typeof thread.lastCompactedMessageId === 'string' ? thread.lastCompactedMessageId : undefined,
67
60
  )
68
61
 
69
62
  const result = await contextCompactionRuntime.compactHistory({
70
- summaryText: typeof workstream.compactionSummary === 'string' ? workstream.compactionSummary : '',
63
+ summaryText: typeof thread.compactionSummary === 'string' ? thread.compactionSummary : '',
71
64
  liveMessages,
72
- tailMessageCount: WORKSTREAM_RAW_TAIL_MESSAGES,
65
+ tailMessageCount: THREAD_RAW_TAIL_MESSAGES,
73
66
  contextSize: params.contextSize,
74
67
  })
75
68
 
@@ -78,21 +71,18 @@ class ContextCompactionService {
78
71
  }
79
72
 
80
73
  if (result.compactedMessages.length > 0) {
81
- await workstreamMessageService.upsertMessages({
82
- workstreamId: params.workstreamId,
83
- messages: result.compactedMessages,
84
- })
74
+ await threadMessageService.upsertMessages({ threadId: params.threadId, messages: result.compactedMessages })
85
75
  }
86
76
 
87
77
  await databaseService.update(
88
- TABLES.WORKSTREAM,
89
- params.workstreamId,
78
+ TABLES.THREAD,
79
+ params.threadId,
90
80
  { compactionSummary: result.summaryText, lastCompactedMessageId: result.lastCompactedMessageId },
91
- WorkstreamSchema,
81
+ ThreadSchema,
92
82
  )
93
83
 
94
84
  this.logCompactionMetrics({
95
- domain: 'workstream',
85
+ domain: 'thread',
96
86
  entityId,
97
87
  inputChars: result.inputChars,
98
88
  outputChars: result.outputChars,
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ChatMessage,
3
+ ExecutionPlanQueryArgs,
3
4
  ExecutionPlanToolResultData,
4
- GetActiveExecutionPlanArgs,
5
5
  ListExecutionPlansSummary,
6
6
  ListExecutionPlansToolResultData,
7
7
  PlanEventRecord,
@@ -9,7 +9,6 @@ import type {
9
9
  PlanNodeRunRecord,
10
10
  PlanNodeSpecRecord,
11
11
  PlanSpecRecord,
12
- ResumeExecutionPlanRunArgs,
13
12
  SerializableExecutionPlan,
14
13
  SubmitPlanTurnResultArgs,
15
14
  SubmitExecutionNodeResultArgs,
@@ -30,7 +29,7 @@ import { databaseService } from '../db/service'
30
29
  import type { DatabaseTransaction } from '../db/service'
31
30
  import { TABLES } from '../db/tables'
32
31
  import { readApprovalContinuationResponse } from '../runtime/approval-continuation'
33
- import { extractMessageText } from '../runtime/workstream-chat-helpers'
32
+ import { extractMessageText } from '../runtime/thread-chat-helpers'
34
33
  import { toDatabaseDateTime } from '../utils/date-time'
35
34
  import { ownershipDispatcherService } from './ownership-dispatcher.service'
36
35
  import { planBuilderService } from './plan-builder.service'
@@ -50,7 +49,7 @@ function aggregateBlockingIssues(issues: Array<{ code: string; message: string }
50
49
  function toSpecData(spec: PlanSpecRecord, patch: Partial<PlanSpecRecord> & { replacedSpecId?: RecordIdInput | null }) {
51
50
  return {
52
51
  organizationId: ensureRecordId(spec.organizationId, TABLES.ORGANIZATION),
53
- workstreamId: ensureRecordId(spec.workstreamId, TABLES.WORKSTREAM),
52
+ threadId: ensureRecordId(spec.threadId, TABLES.THREAD),
54
53
  title: patch.title ?? spec.title,
55
54
  objective: patch.objective ?? spec.objective,
56
55
  version: patch.version ?? spec.version,
@@ -105,7 +104,7 @@ function buildApprovalResponseFromMessages(
105
104
 
106
105
  function buildCompiledSpecCreateData(params: {
107
106
  organizationId: RecordIdInput
108
- workstreamId: RecordIdInput
107
+ threadId: RecordIdInput
109
108
  leadAgentId: string
110
109
  compiled: ReturnType<typeof planCompilerService.compile>
111
110
  version: number
@@ -114,7 +113,7 @@ function buildCompiledSpecCreateData(params: {
114
113
  }) {
115
114
  return {
116
115
  organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
117
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
116
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
118
117
  title: params.compiled.draft.title,
119
118
  objective: params.compiled.draft.objective,
120
119
  version: params.version,
@@ -136,21 +135,21 @@ function buildCompiledSpecCreateData(params: {
136
135
  }
137
136
 
138
137
  class ExecutionPlanService {
139
- async hasActivePlan(workstreamId: RecordIdInput): Promise<boolean> {
140
- return (await planRunService.getActiveRunRecord(workstreamId)) !== null
138
+ async hasActivePlan(threadId: RecordIdInput): Promise<boolean> {
139
+ return (await planRunService.getActiveRunRecord(threadId)) !== null
141
140
  }
142
141
 
143
- async getActivePlanForWorkstream(
144
- workstreamId: RecordIdInput,
145
- options?: Partial<GetActiveExecutionPlanArgs> & { runId?: RecordIdInput },
142
+ async getActivePlanForThread(
143
+ threadId: RecordIdInput,
144
+ options?: Partial<ExecutionPlanQueryArgs> & { runId?: RecordIdInput },
146
145
  ): Promise<SerializableExecutionPlan | null> {
147
- const plans = await this.getActivePlansForWorkstream(workstreamId, options)
146
+ const plans = await this.getActivePlansForThread(threadId, options)
148
147
  return plans[0] ?? null
149
148
  }
150
149
 
151
- async getActivePlansForWorkstream(
152
- workstreamId: RecordIdInput,
153
- options?: Partial<GetActiveExecutionPlanArgs> & { runId?: RecordIdInput },
150
+ async getActivePlansForThread(
151
+ threadId: RecordIdInput,
152
+ options?: Partial<ExecutionPlanQueryArgs> & { runId?: RecordIdInput },
154
153
  ): Promise<SerializableExecutionPlan[]> {
155
154
  if (options?.runId) {
156
155
  const run = await planRunService.getRunById(options.runId)
@@ -165,7 +164,7 @@ class ExecutionPlanService {
165
164
  ]
166
165
  }
167
166
 
168
- const runs = await planRunService.getActiveRunRecords(workstreamId)
167
+ const runs = await planRunService.getActiveRunRecords(threadId)
169
168
  if (runs.length === 0) return []
170
169
 
171
170
  return await Promise.all(
@@ -181,8 +180,8 @@ class ExecutionPlanService {
181
180
  )
182
181
  }
183
182
 
184
- async listActivePlanSummaries(workstreamId: RecordIdInput): Promise<ListExecutionPlansToolResultData> {
185
- const runs = await planRunService.getActiveRunRecords(workstreamId)
183
+ async listActivePlanSummaries(threadId: RecordIdInput): Promise<ListExecutionPlansToolResultData> {
184
+ const runs = await planRunService.getActiveRunRecords(threadId)
186
185
  const plans: ListExecutionPlansSummary[] = await Promise.all(
187
186
  runs.map(async (run) => {
188
187
  const spec = await planRunService.getPlanSpecById(run.planSpecId)
@@ -202,7 +201,7 @@ class ExecutionPlanService {
202
201
  }
203
202
 
204
203
  async getActivePlanToolResult(params: {
205
- workstreamId: RecordIdInput
204
+ threadId: RecordIdInput
206
205
  runId?: string
207
206
  includeEvents?: boolean
208
207
  includeArtifacts?: boolean
@@ -225,36 +224,26 @@ class ExecutionPlanService {
225
224
  return buildExecutionPlanToolResult({ action: 'loaded', plan, message: `Loaded execution run "${plan.title}".` })
226
225
  }
227
226
 
228
- const runs = await planRunService.getActiveRunRecords(params.workstreamId)
227
+ const runs = await planRunService.getActiveRunRecords(params.threadId)
229
228
  if (runs.length === 0) {
230
229
  return buildExecutionPlanToolResult({ action: 'none', plan: null, message: 'No active execution run.' })
231
230
  }
232
231
 
233
232
  const plan = await planRunService.toSerializablePlan(runs[0], serializeOptions)
234
- const planSummaries = await Promise.all(
235
- runs.map(async (run) => {
236
- const spec = await planRunService.getPlanSpecById(run.planSpecId)
237
- return { runId: recordIdToString(run.id, TABLES.PLAN_RUN), title: spec.title, status: run.status }
238
- }),
239
- )
240
233
 
241
- return {
242
- ...buildExecutionPlanToolResult({
243
- action: 'loaded',
244
- plan,
245
- message:
246
- runs.length === 1
247
- ? `Loaded execution run "${plan.title}".`
248
- : `Loaded ${runs.length} active execution runs. Showing most recent: "${plan.title}".`,
249
- }),
250
- planCount: runs.length,
251
- planSummaries,
252
- }
234
+ return buildExecutionPlanToolResult({
235
+ action: 'loaded',
236
+ plan,
237
+ message:
238
+ runs.length === 1
239
+ ? `Loaded execution run "${plan.title}".`
240
+ : `Loaded ${runs.length} active execution runs. Showing most recent: "${plan.title}".`,
241
+ })
253
242
  }
254
243
 
255
244
  async createPlan(params: {
256
245
  organizationId: RecordIdInput
257
- workstreamId: RecordIdInput
246
+ threadId: RecordIdInput
258
247
  leadAgentId: string
259
248
  input: PlanDraft
260
249
  }): Promise<ExecutionPlanToolResultData> {
@@ -277,7 +266,7 @@ class ExecutionPlanService {
277
266
  .content(
278
267
  buildCompiledSpecCreateData({
279
268
  organizationId: params.organizationId,
280
- workstreamId: params.workstreamId,
269
+ threadId: params.threadId,
281
270
  leadAgentId: params.leadAgentId,
282
271
  compiled,
283
272
  version: 1,
@@ -290,7 +279,7 @@ class ExecutionPlanService {
290
279
  runId,
291
280
  spec,
292
281
  organizationId: params.organizationId,
293
- workstreamId: params.workstreamId,
282
+ threadId: params.threadId,
294
283
  leadAgentId: params.leadAgentId,
295
284
  nodes: compiled.nodes,
296
285
  emittedEvents,
@@ -307,7 +296,7 @@ class ExecutionPlanService {
307
296
  if (compiled.draft.schedule) {
308
297
  const schedule = await planSchedulerService.createSchedule({
309
298
  organizationId: params.organizationId,
310
- workstreamId: params.workstreamId,
299
+ threadId: params.threadId,
311
300
  planSpecId: specId,
312
301
  runId,
313
302
  scheduleSpec: compiled.draft.schedule,
@@ -330,17 +319,17 @@ class ExecutionPlanService {
330
319
  }
331
320
 
332
321
  async replacePlan(params: {
333
- workstreamId: RecordIdInput
322
+ threadId: RecordIdInput
334
323
  organizationId: RecordIdInput
335
324
  leadAgentId: string
336
325
  input: PlanDraft & { runId: string; reason: string }
337
326
  }): Promise<ExecutionPlanToolResultData> {
338
327
  const activeRun = await planRunService.getRunById(params.input.runId)
339
- const resolvedWorkstreamId = activeRun.workstreamId
328
+ const resolvedThreadId = activeRun.threadId
340
329
 
341
- const activeRuns = await planRunService.getActiveRunRecords(resolvedWorkstreamId)
330
+ const activeRuns = await planRunService.getActiveRunRecords(resolvedThreadId)
342
331
  if (activeRuns.length === 0) {
343
- throw new Error('No active execution run exists for this workstream.')
332
+ throw new Error('No active execution run exists for this thread.')
344
333
  }
345
334
  if (!activeRuns.some((run) => recordIdToString(run.id, TABLES.PLAN_RUN) === params.input.runId)) {
346
335
  throw new Error('Only an active execution run can be replaced.')
@@ -399,7 +388,7 @@ class ExecutionPlanService {
399
388
  .content(
400
389
  buildCompiledSpecCreateData({
401
390
  organizationId: params.organizationId,
402
- workstreamId: resolvedWorkstreamId,
391
+ threadId: resolvedThreadId,
403
392
  leadAgentId: params.leadAgentId,
404
393
  compiled,
405
394
  version: supersededSpec.version + 1,
@@ -413,7 +402,7 @@ class ExecutionPlanService {
413
402
  runId,
414
403
  spec,
415
404
  organizationId: params.organizationId,
416
- workstreamId: resolvedWorkstreamId,
405
+ threadId: resolvedThreadId,
417
406
  leadAgentId: params.leadAgentId,
418
407
  nodes: compiled.nodes,
419
408
  emittedEvents,
@@ -440,28 +429,29 @@ class ExecutionPlanService {
440
429
  }
441
430
 
442
431
  async submitNodeResult(params: {
443
- workstreamId: RecordIdInput
432
+ threadId: RecordIdInput
444
433
  emittedBy: string
445
434
  input: SubmitExecutionNodeResultArgs
446
435
  }): Promise<ExecutionPlanToolResultData> {
436
+ const { runId, nodeId, ...result } = params.input
447
437
  return await this.submitPlanTurnResult({
448
- workstreamId: params.workstreamId,
438
+ threadId: params.threadId,
449
439
  emittedBy: params.emittedBy,
450
- runId: params.input.runId,
451
- nodeId: params.input.nodeId,
452
- input: params.input.result,
440
+ runId,
441
+ nodeId,
442
+ input: result,
453
443
  })
454
444
  }
455
445
 
456
446
  async submitPlanTurnResult(params: {
457
- workstreamId: RecordIdInput
447
+ threadId: RecordIdInput
458
448
  runId: string
459
449
  nodeId: string
460
450
  emittedBy: string
461
451
  input: SubmitPlanTurnResultArgs
462
452
  }): Promise<ExecutionPlanToolResultData> {
463
453
  const result = await planExecutorService.submitNodeResult({
464
- workstreamId: params.workstreamId,
454
+ threadId: params.threadId,
465
455
  runId: params.runId,
466
456
  nodeId: params.nodeId,
467
457
  emittedBy: params.emittedBy,
@@ -476,17 +466,16 @@ class ExecutionPlanService {
476
466
  action: result.action,
477
467
  plan,
478
468
  message: result.message ?? `Submitted result for node "${params.nodeId}".`,
479
- changedNodeId: result.changedNodeId ?? undefined,
480
469
  })
481
470
  }
482
471
 
483
472
  async resumeRun(params: {
484
- workstreamId: RecordIdInput
473
+ threadId: RecordIdInput
485
474
  emittedBy: string
486
- input: ResumeExecutionPlanRunArgs
475
+ input: { runId: string }
487
476
  }): Promise<ExecutionPlanToolResultData> {
488
477
  const result = await planExecutorService.resumeRun({
489
- workstreamId: params.workstreamId,
478
+ threadId: params.threadId,
490
479
  runId: params.input.runId,
491
480
  emittedBy: params.emittedBy,
492
481
  })
@@ -499,12 +488,11 @@ class ExecutionPlanService {
499
488
  action: result.action,
500
489
  plan,
501
490
  message: result.message ?? `Resumed execution run "${params.input.runId}".`,
502
- changedNodeId: result.changedNodeId ?? undefined,
503
491
  })
504
492
  }
505
493
 
506
494
  async applyApprovalResponseFromMessages(params: {
507
- workstreamId: RecordIdInput
495
+ threadId: RecordIdInput
508
496
  approvalMessages: ChatMessage[]
509
497
  respondedBy: string
510
498
  }): Promise<SerializableExecutionPlan | null> {
@@ -512,22 +500,22 @@ class ExecutionPlanService {
512
500
  if (!approvalResponse) return null
513
501
 
514
502
  return await this.respondToApproval({
515
- workstreamId: params.workstreamId,
503
+ threadId: params.threadId,
516
504
  emittedBy: params.respondedBy,
517
505
  input: approvalResponse,
518
506
  })
519
507
  }
520
508
 
521
509
  async respondToApproval(params: {
522
- workstreamId: RecordIdInput
510
+ threadId: RecordIdInput
523
511
  emittedBy: string
524
512
  input: { approvalId: string; response: Record<string, unknown>; approvalMessageId?: string }
525
513
  }): Promise<SerializableExecutionPlan | null> {
526
- const run = await planRunService.getActiveRunRecord(params.workstreamId)
514
+ const run = await planRunService.getActiveRunRecord(params.threadId)
527
515
  if (!run) return null
528
516
 
529
517
  const plan = await planExecutorService.submitHumanNodeResponse({
530
- workstreamId: params.workstreamId,
518
+ threadId: params.threadId,
531
519
  approvalId: params.input.approvalId,
532
520
  respondedBy: params.emittedBy,
533
521
  response: params.input.response,
@@ -539,11 +527,11 @@ class ExecutionPlanService {
539
527
  }
540
528
 
541
529
  async applyHumanInputFromUserMessage(params: {
542
- workstreamId: RecordIdInput
530
+ threadId: RecordIdInput
543
531
  message: ChatMessage
544
532
  respondedBy: string
545
533
  }): Promise<SerializableExecutionPlan | null> {
546
- const run = await planRunService.getActiveRunRecord(params.workstreamId)
534
+ const run = await planRunService.getActiveRunRecord(params.threadId)
547
535
  if (!run || run.status !== 'awaiting-human' || !run.waitingNodeId) return null
548
536
 
549
537
  const nodeSpec = await planRunService.getNodeSpecByNodeId(run.planSpecId, run.waitingNodeId)
@@ -565,7 +553,7 @@ class ExecutionPlanService {
565
553
  } satisfies Record<string, unknown>
566
554
 
567
555
  const plan = await planExecutorService.submitHumanNodeResponse({
568
- workstreamId: params.workstreamId,
556
+ threadId: params.threadId,
569
557
  respondedBy: params.respondedBy,
570
558
  response,
571
559
  approvalMessageId: params.message.id,
@@ -677,7 +665,7 @@ class ExecutionPlanService {
677
665
  runId: RecordIdInput
678
666
  spec: PlanSpecRecord
679
667
  organizationId: RecordIdInput
680
- workstreamId: RecordIdInput
668
+ threadId: RecordIdInput
681
669
  leadAgentId: string
682
670
  nodes: CompiledPlanNode[]
683
671
  emittedEvents: PlanEventRecord[]
@@ -694,7 +682,7 @@ class ExecutionPlanService {
694
682
  .content({
695
683
  planSpecId: ensureRecordId(params.spec.id, TABLES.PLAN_SPEC),
696
684
  organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
697
- workstreamId: ensureRecordId(params.workstreamId, TABLES.WORKSTREAM),
685
+ threadId: ensureRecordId(params.threadId, TABLES.THREAD),
698
686
  leadAgentId: params.leadAgentId,
699
687
  status: 'running',
700
688
  readyNodeIds: [],