@lota-sdk/core 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ai/definitions.ts +5 -59
- package/src/ai-gateway/ai-gateway.ts +36 -28
- package/src/ai-gateway/cache-headers.ts +9 -0
- package/src/config/model-constants.ts +6 -2
- package/src/create-runtime.ts +5 -17
- package/src/db/memory-types.ts +13 -8
- package/src/db/memory.ts +74 -53
- package/src/queues/autonomous-job.queue.ts +1 -8
- package/src/queues/context-compaction.queue.ts +2 -2
- package/src/queues/index.ts +2 -6
- package/src/queues/organization-learning.queue.ts +78 -0
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -16
- package/src/queues/title-generation.queue.ts +62 -0
- package/src/runtime/agent-prompt-context.ts +0 -18
- package/src/runtime/agent-runtime-policy.ts +9 -2
- package/src/runtime/context-compaction-constants.ts +4 -2
- package/src/runtime/context-compaction.ts +135 -118
- package/src/runtime/execution-plan.ts +2 -1
- package/src/runtime/memory-pipeline.ts +70 -1
- package/src/runtime/memory-prompts-fact.ts +16 -0
- package/src/runtime/plugin-resolution.ts +3 -2
- package/src/runtime/plugin-types.ts +1 -42
- package/src/runtime/post-turn-side-effects.ts +212 -0
- package/src/runtime/runtime-config.ts +0 -13
- package/src/runtime/runtime-extensions.ts +10 -16
- package/src/runtime/runtime-worker-registry.ts +8 -19
- package/src/runtime/social-chat-agent-runner.ts +119 -0
- package/src/runtime/social-chat-history.ts +110 -0
- package/src/runtime/social-chat-prompts.ts +58 -0
- package/src/runtime/social-chat.ts +104 -340
- package/src/runtime/specialist-runner.ts +18 -0
- package/src/runtime/workstream-chat-helpers.ts +19 -0
- package/src/runtime/workstream-plan-turn.ts +195 -0
- package/src/runtime/workstream-state.ts +11 -8
- package/src/runtime/workstream-turn-context.ts +183 -0
- package/src/services/agent-activity.service.ts +350 -0
- package/src/services/autonomous-job.service.ts +1 -8
- package/src/services/execution-plan.service.ts +205 -334
- package/src/services/index.ts +2 -4
- package/src/services/memory.service.ts +54 -44
- package/src/services/ownership-dispatcher.service.ts +2 -19
- package/src/services/plan-completion-side-effects.ts +80 -0
- package/src/services/plan-event-delivery.service.ts +1 -1
- package/src/services/plan-executor.service.ts +42 -190
- package/src/services/plan-node-spec.ts +60 -0
- package/src/services/plan-run-data.ts +88 -0
- package/src/services/plan-validator.service.ts +10 -8
- package/src/services/workstream-constants.ts +2 -0
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.service.ts +208 -715
- package/src/services/workstream.service.ts +162 -192
- package/src/services/workstream.types.ts +12 -44
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -0
- package/src/tools/execution-plan.tool.ts +11 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/project-with-plan.tool.ts +87 -0
- package/src/tools/remember-memory.tool.ts +7 -10
- package/src/tools/research-topic.tool.ts +1 -1
- package/src/tools/team-think.tool.ts +1 -1
- package/src/tools/user-questions.tool.ts +1 -1
- package/src/utils/autonomous-job-ids.ts +7 -0
- package/src/workers/organization-learning.worker.ts +31 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +9 -3
- package/src/workers/skill-extraction.runner.ts +2 -2
- package/src/queues/recent-activity-title-refinement.queue.ts +0 -30
- package/src/queues/regular-chat-memory-digest.config.ts +0 -12
- package/src/queues/regular-chat-memory-digest.queue.ts +0 -34
- package/src/queues/skill-extraction.config.ts +0 -9
- package/src/queues/skill-extraction.queue.ts +0 -27
- package/src/queues/workstream-title-generation.queue.ts +0 -33
- package/src/services/context-enrichment.service.ts +0 -33
- package/src/services/coordination-registry.service.ts +0 -117
- package/src/services/domain-agent-executor.service.ts +0 -71
- package/src/services/memory-assessment.service.ts +0 -44
- package/src/services/playbook-registry.service.ts +0 -67
- package/src/workers/regular-chat-memory-digest.worker.ts +0 -22
- package/src/workers/skill-extraction.worker.ts +0 -22
|
@@ -11,10 +11,6 @@ import { databaseService } from '../db/service'
|
|
|
11
11
|
import type { DatabaseTable } from '../db/tables'
|
|
12
12
|
import { TABLES } from '../db/tables'
|
|
13
13
|
import { getRedisConnection, withRedisLeaseLock } from '../redis'
|
|
14
|
-
import {
|
|
15
|
-
MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES,
|
|
16
|
-
MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES,
|
|
17
|
-
} from '../runtime/context-compaction-constants'
|
|
18
14
|
import {
|
|
19
15
|
appendToMemoryBlock,
|
|
20
16
|
compactMemoryBlockEntries,
|
|
@@ -25,44 +21,13 @@ import {
|
|
|
25
21
|
import { toIsoDateTimeString } from '../utils/date-time'
|
|
26
22
|
import { chatRunRegistry } from './chat-run-registry.service'
|
|
27
23
|
import { contextCompactionService } from './context-compaction.service'
|
|
24
|
+
import { MEMORY_BLOCK_COMPACTION_CHUNK_ENTRIES, MEMORY_BLOCK_COMPACTION_TRIGGER_ENTRIES } from './workstream-constants'
|
|
28
25
|
import { workstreamMessageService } from './workstream-message.service'
|
|
29
|
-
import { WorkstreamSchema } from './workstream.types'
|
|
30
|
-
import type { NormalizedWorkstream, WorkstreamRecord } from './workstream.types'
|
|
26
|
+
import { NormalizedWorkstreamSchema, PublicWorkstreamSchema, WorkstreamSchema } from './workstream.types'
|
|
27
|
+
import type { NormalizedWorkstream, PublicWorkstream, WorkstreamRecord } from './workstream.types'
|
|
31
28
|
|
|
32
29
|
// Uses SurrealQL directly to keep pagination/order logic close to queries.
|
|
33
30
|
|
|
34
|
-
const LIST_WORKSTREAMS_QUERY = `SELECT * FROM ${TABLES.WORKSTREAM}
|
|
35
|
-
WHERE userId = $userId
|
|
36
|
-
AND organizationId = $orgId
|
|
37
|
-
AND mode = $mode
|
|
38
|
-
AND core = $core
|
|
39
|
-
ORDER BY updatedAt DESC
|
|
40
|
-
LIMIT $limit START $offset`
|
|
41
|
-
|
|
42
|
-
const LIST_WORKSTREAMS_EXCLUDE_ARCHIVED_QUERY = `SELECT * FROM ${TABLES.WORKSTREAM}
|
|
43
|
-
WHERE userId = $userId
|
|
44
|
-
AND organizationId = $orgId
|
|
45
|
-
AND mode = $mode
|
|
46
|
-
AND core = $core
|
|
47
|
-
AND status = "regular"
|
|
48
|
-
ORDER BY updatedAt DESC
|
|
49
|
-
LIMIT $limit START $offset`
|
|
50
|
-
|
|
51
|
-
const LIST_ALL_WORKSTREAMS_BY_MODE_QUERY = `SELECT * FROM ${TABLES.WORKSTREAM}
|
|
52
|
-
WHERE userId = $userId
|
|
53
|
-
AND organizationId = $orgId
|
|
54
|
-
AND mode = $mode
|
|
55
|
-
AND core = $core
|
|
56
|
-
ORDER BY updatedAt DESC`
|
|
57
|
-
|
|
58
|
-
const LIST_ALL_WORKSTREAMS_BY_MODE_EXCLUDE_ARCHIVED_QUERY = `SELECT * FROM ${TABLES.WORKSTREAM}
|
|
59
|
-
WHERE userId = $userId
|
|
60
|
-
AND organizationId = $orgId
|
|
61
|
-
AND mode = $mode
|
|
62
|
-
AND core = $core
|
|
63
|
-
AND status = "regular"
|
|
64
|
-
ORDER BY updatedAt DESC`
|
|
65
|
-
|
|
66
31
|
const WORKSTREAM_ACTIVE_RUN_LOCK_TTL_MS = 90_000
|
|
67
32
|
const WORKSTREAM_ACTIVE_RUN_LOCK_MAX_WAIT_MS = 750
|
|
68
33
|
const WORKSTREAM_ACTIVE_RUN_LOCK_RETRY_DELAY_MS = 75
|
|
@@ -116,6 +81,33 @@ function buildActiveRunLockKey(workstreamId: RecordIdRef): string {
|
|
|
116
81
|
return `workstream-active-run:${recordIdToString(ensureRecordId(workstreamId, TABLES.WORKSTREAM), TABLES.WORKSTREAM)}`
|
|
117
82
|
}
|
|
118
83
|
|
|
84
|
+
function buildListWorkstreamsQuery(options: { includeArchived: boolean; paginate: boolean }): string {
|
|
85
|
+
const clauses = [
|
|
86
|
+
`SELECT * FROM ${TABLES.WORKSTREAM}`,
|
|
87
|
+
'WHERE userId = $userId',
|
|
88
|
+
' AND organizationId = $orgId',
|
|
89
|
+
' AND mode = $mode',
|
|
90
|
+
' AND core = $core',
|
|
91
|
+
]
|
|
92
|
+
if (!options.includeArchived) {
|
|
93
|
+
clauses.push(' AND status = "regular"')
|
|
94
|
+
}
|
|
95
|
+
clauses.push('ORDER BY updatedAt DESC')
|
|
96
|
+
if (options.paginate) {
|
|
97
|
+
clauses.push('LIMIT $limit START $offset')
|
|
98
|
+
}
|
|
99
|
+
return clauses.join('\n')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizeActiveTurnValue(value: unknown): string | null {
|
|
103
|
+
if (typeof value !== 'string') {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const normalized = value.trim()
|
|
108
|
+
return normalized.length > 0 ? normalized : null
|
|
109
|
+
}
|
|
110
|
+
|
|
119
111
|
export class ActiveWorkstreamRunConflictError extends Error {
|
|
120
112
|
constructor() {
|
|
121
113
|
super('A chat run is already active.')
|
|
@@ -201,37 +193,16 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
201
193
|
if (mode === 'direct') {
|
|
202
194
|
const agentId = requireDirectAgentId(directAgentId)
|
|
203
195
|
const directWorkstreamId = buildDirectWorkstreamId({ userId, orgId, agentId })
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
TABLES.WORKSTREAM,
|
|
215
|
-
directWorkstreamId,
|
|
216
|
-
{ userId, organizationId: orgId, mode, core: false, agentId, title, status: 'regular', nameGenerated: true },
|
|
217
|
-
WorkstreamSchema,
|
|
218
|
-
)
|
|
219
|
-
.catch((error) => {
|
|
220
|
-
createError = error
|
|
221
|
-
return null
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
if (!workstream) {
|
|
225
|
-
workstream = await this.findById(directWorkstreamId)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (!workstream) {
|
|
229
|
-
if (createError instanceof Error) {
|
|
230
|
-
throw createError
|
|
231
|
-
}
|
|
232
|
-
throw new Error('Failed to create or load direct workstream')
|
|
233
|
-
}
|
|
234
|
-
|
|
196
|
+
const workstream = await this.upsertDeterministicWorkstream(directWorkstreamId, {
|
|
197
|
+
userId,
|
|
198
|
+
organizationId: orgId,
|
|
199
|
+
mode,
|
|
200
|
+
core: false,
|
|
201
|
+
agentId,
|
|
202
|
+
title,
|
|
203
|
+
status: 'regular',
|
|
204
|
+
nameGenerated: true,
|
|
205
|
+
})
|
|
235
206
|
return await this.toNormalizedWorkstream(workstream)
|
|
236
207
|
}
|
|
237
208
|
|
|
@@ -239,46 +210,17 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
239
210
|
const resolvedCoreType = requireString(coreType)
|
|
240
211
|
const coreProfile = getCoreWorkstreamProfile(resolvedCoreType)
|
|
241
212
|
const coreWorkstreamId = buildCoreWorkstreamId({ userId, orgId, coreType: resolvedCoreType })
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{
|
|
254
|
-
userId,
|
|
255
|
-
organizationId: orgId,
|
|
256
|
-
mode,
|
|
257
|
-
core: true,
|
|
258
|
-
coreType: resolvedCoreType,
|
|
259
|
-
agentId: coreProfile.config.agentId,
|
|
260
|
-
title,
|
|
261
|
-
status: 'regular',
|
|
262
|
-
nameGenerated: true,
|
|
263
|
-
},
|
|
264
|
-
WorkstreamSchema,
|
|
265
|
-
)
|
|
266
|
-
.catch((error) => {
|
|
267
|
-
createError = error
|
|
268
|
-
return null
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
if (!workstream) {
|
|
272
|
-
workstream = await this.findById(coreWorkstreamId)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (!workstream) {
|
|
276
|
-
if (createError instanceof Error) {
|
|
277
|
-
throw createError
|
|
278
|
-
}
|
|
279
|
-
throw new Error('Failed to create or load core workstream')
|
|
280
|
-
}
|
|
281
|
-
|
|
213
|
+
const workstream = await this.upsertDeterministicWorkstream(coreWorkstreamId, {
|
|
214
|
+
userId,
|
|
215
|
+
organizationId: orgId,
|
|
216
|
+
mode,
|
|
217
|
+
core: true,
|
|
218
|
+
coreType: resolvedCoreType,
|
|
219
|
+
agentId: coreProfile.config.agentId,
|
|
220
|
+
title,
|
|
221
|
+
status: 'regular',
|
|
222
|
+
nameGenerated: true,
|
|
223
|
+
})
|
|
282
224
|
return await this.toNormalizedWorkstream(workstream)
|
|
283
225
|
}
|
|
284
226
|
|
|
@@ -395,22 +337,23 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
395
337
|
}
|
|
396
338
|
|
|
397
339
|
if (options.mode === 'direct' || core) {
|
|
398
|
-
const query = includeArchived
|
|
399
|
-
? LIST_ALL_WORKSTREAMS_BY_MODE_QUERY
|
|
400
|
-
: LIST_ALL_WORKSTREAMS_BY_MODE_EXCLUDE_ARCHIVED_QUERY
|
|
401
340
|
const workstreams = await databaseService.queryMany<typeof WorkstreamSchema>(
|
|
402
|
-
new BoundQuery(
|
|
341
|
+
new BoundQuery(buildListWorkstreamsQuery({ includeArchived, paginate: false }), {
|
|
342
|
+
userId,
|
|
343
|
+
orgId,
|
|
344
|
+
mode: options.mode,
|
|
345
|
+
core,
|
|
346
|
+
}),
|
|
403
347
|
WorkstreamSchema,
|
|
404
348
|
)
|
|
405
349
|
|
|
406
|
-
return { workstreams: await this.toNormalizedWorkstreams(workstreams), hasMore: false }
|
|
350
|
+
return { workstreams: await this.toNormalizedWorkstreams(workstreams, { checkLease: false }), hasMore: false }
|
|
407
351
|
}
|
|
408
352
|
|
|
409
353
|
const take = options.take ?? WORKSTREAM.DEFAULT_PAGE_LIMIT
|
|
410
354
|
const page = options.page ?? 1
|
|
411
|
-
const query = includeArchived ? LIST_WORKSTREAMS_QUERY : LIST_WORKSTREAMS_EXCLUDE_ARCHIVED_QUERY
|
|
412
355
|
const workstreams = await databaseService.queryMany<typeof WorkstreamSchema>(
|
|
413
|
-
new BoundQuery(
|
|
356
|
+
new BoundQuery(buildListWorkstreamsQuery({ includeArchived, paginate: true }), {
|
|
414
357
|
userId,
|
|
415
358
|
orgId,
|
|
416
359
|
mode: options.mode,
|
|
@@ -424,7 +367,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
424
367
|
const hasMore = workstreams.length > take
|
|
425
368
|
const sliced = hasMore ? workstreams.slice(0, take) : workstreams
|
|
426
369
|
|
|
427
|
-
return { workstreams: await this.toNormalizedWorkstreams(sliced), hasMore }
|
|
370
|
+
return { workstreams: await this.toNormalizedWorkstreams(sliced, { checkLease: false }), hasMore }
|
|
428
371
|
}
|
|
429
372
|
|
|
430
373
|
async listOrganizationWorkstreams(params: {
|
|
@@ -466,7 +409,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
466
409
|
WorkstreamSchema,
|
|
467
410
|
)
|
|
468
411
|
|
|
469
|
-
return await this.toNormalizedWorkstreams(workstreams)
|
|
412
|
+
return await this.toNormalizedWorkstreams(workstreams, { checkLease: false })
|
|
470
413
|
}
|
|
471
414
|
|
|
472
415
|
async getWorkstream(workstreamId: RecordIdRef): Promise<NormalizedWorkstream> {
|
|
@@ -489,28 +432,35 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
489
432
|
return await this.toNormalizedWorkstream(workstream)
|
|
490
433
|
}
|
|
491
434
|
|
|
492
|
-
async
|
|
435
|
+
async setActiveTurn(workstreamId: RecordIdRef, runId: string, streamId?: string | null): Promise<void> {
|
|
493
436
|
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
494
|
-
if (
|
|
437
|
+
if (streamId === null || streamId === undefined) {
|
|
495
438
|
await databaseService.query<unknown>(surql`
|
|
496
439
|
UPDATE ONLY ${workstreamRef}
|
|
497
|
-
SET activeRunId =
|
|
440
|
+
SET activeRunId = ${runId},
|
|
441
|
+
activeStreamId = NONE
|
|
498
442
|
`)
|
|
499
443
|
return
|
|
500
444
|
}
|
|
501
445
|
|
|
502
446
|
await databaseService.query<unknown>(surql`
|
|
503
447
|
UPDATE ONLY ${workstreamRef}
|
|
504
|
-
SET activeRunId = ${runId}
|
|
448
|
+
SET activeRunId = ${runId},
|
|
449
|
+
activeStreamId = ${streamId}
|
|
505
450
|
`)
|
|
506
451
|
}
|
|
507
452
|
|
|
508
|
-
async
|
|
453
|
+
async getActiveTurn(workstreamId: RecordIdRef): Promise<{ runId: string | null; streamId: string | null }> {
|
|
509
454
|
const workstream = await this.getById(workstreamId)
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
455
|
+
return {
|
|
456
|
+
runId: normalizeActiveTurnValue(workstream.activeRunId),
|
|
457
|
+
streamId: normalizeActiveTurnValue(workstream.activeStreamId),
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async getActiveRunId(workstreamId: RecordIdRef): Promise<string | null> {
|
|
462
|
+
const { runId } = await this.getActiveTurn(workstreamId)
|
|
463
|
+
return runId
|
|
514
464
|
}
|
|
515
465
|
|
|
516
466
|
async hasActiveRunLease(workstreamId: RecordIdRef): Promise<boolean> {
|
|
@@ -540,52 +490,43 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
540
490
|
}
|
|
541
491
|
}
|
|
542
492
|
|
|
543
|
-
async
|
|
544
|
-
const
|
|
545
|
-
|
|
493
|
+
async getActiveStreamId(workstreamId: RecordIdRef): Promise<string | null> {
|
|
494
|
+
const { streamId } = await this.getActiveTurn(workstreamId)
|
|
495
|
+
return streamId
|
|
546
496
|
}
|
|
547
497
|
|
|
548
|
-
async
|
|
498
|
+
async clearActiveTurn(workstreamId: RecordIdRef, params: { runId: string; streamId?: string | null }): Promise<void> {
|
|
549
499
|
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
550
|
-
|
|
500
|
+
const currentStreamId = params.streamId ?? null
|
|
501
|
+
if (currentStreamId === null) {
|
|
502
|
+
await databaseService.query(
|
|
503
|
+
surql`UPDATE ONLY ${workstreamRef} SET activeRunId = NONE, activeStreamId = NONE WHERE activeRunId = ${params.runId}`,
|
|
504
|
+
)
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
await databaseService.query(surql`
|
|
551
509
|
UPDATE ONLY ${workstreamRef}
|
|
552
|
-
SET
|
|
510
|
+
SET activeRunId = NONE,
|
|
511
|
+
activeStreamId = NONE
|
|
512
|
+
WHERE activeRunId = ${params.runId} AND activeStreamId = ${currentStreamId}
|
|
553
513
|
`)
|
|
554
514
|
}
|
|
555
515
|
|
|
556
|
-
async getActiveStreamId(workstreamId: RecordIdRef): Promise<string | null> {
|
|
557
|
-
const workstream = await this.getById(workstreamId)
|
|
558
|
-
const activeStreamId = workstream.activeStreamId
|
|
559
|
-
if (typeof activeStreamId !== 'string') return null
|
|
560
|
-
const normalized = activeStreamId.trim()
|
|
561
|
-
return normalized.length > 0 ? normalized : null
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
async clearActiveStreamIdIfMatches(workstreamId: RecordIdRef, streamId: string): Promise<void> {
|
|
565
|
-
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
566
|
-
await databaseService.query(
|
|
567
|
-
surql`UPDATE ONLY ${workstreamRef} SET activeStreamId = NONE WHERE activeStreamId = ${streamId}`,
|
|
568
|
-
)
|
|
569
|
-
}
|
|
570
|
-
|
|
571
516
|
async clearStaleActiveRunIfMissingFromRegistry(workstreamId: RecordIdRef): Promise<boolean> {
|
|
572
|
-
const activeRunId = await this.
|
|
517
|
+
const { runId: activeRunId, streamId: activeStreamId } = await this.getActiveTurn(workstreamId)
|
|
573
518
|
if (!activeRunId || (await this.hasActiveRunLease(workstreamId))) {
|
|
574
519
|
return false
|
|
575
520
|
}
|
|
576
521
|
|
|
577
|
-
|
|
578
|
-
await Promise.all([
|
|
579
|
-
this.clearActiveRunIdIfMatches(workstreamId, activeRunId),
|
|
580
|
-
activeStreamId ? this.clearActiveStreamIdIfMatches(workstreamId, activeStreamId) : Promise.resolve(),
|
|
581
|
-
])
|
|
522
|
+
await this.clearActiveTurn(workstreamId, { runId: activeRunId, streamId: activeStreamId })
|
|
582
523
|
|
|
583
524
|
serverLogger.warn`Cleared stale workstream run after lease expired: workstream=${recordIdToString(ensureRecordId(workstreamId, TABLES.WORKSTREAM), TABLES.WORKSTREAM)} run=${activeRunId}`
|
|
584
525
|
return true
|
|
585
526
|
}
|
|
586
527
|
|
|
587
528
|
async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
|
|
588
|
-
const activeRunId = await this.
|
|
529
|
+
const { runId: activeRunId } = await this.getActiveTurn(workstreamId)
|
|
589
530
|
if (!activeRunId) return false
|
|
590
531
|
|
|
591
532
|
const stopped = chatRunRegistry.stop(activeRunId, new DOMException('Run stopped by user.', 'AbortError'))
|
|
@@ -597,19 +538,11 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
597
538
|
return false
|
|
598
539
|
}
|
|
599
540
|
|
|
600
|
-
async
|
|
541
|
+
async setCompacting(workstreamId: RecordIdRef, value: boolean): Promise<void> {
|
|
601
542
|
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
602
543
|
await databaseService.query<unknown>(surql`
|
|
603
544
|
UPDATE ONLY ${workstreamRef}
|
|
604
|
-
SET isCompacting = ${
|
|
605
|
-
`)
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async clearCompacting(workstreamId: RecordIdRef): Promise<void> {
|
|
609
|
-
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
610
|
-
await databaseService.query<unknown>(surql`
|
|
611
|
-
UPDATE ONLY ${workstreamRef}
|
|
612
|
-
SET isCompacting = ${false}
|
|
545
|
+
SET isCompacting = ${value}
|
|
613
546
|
`)
|
|
614
547
|
}
|
|
615
548
|
|
|
@@ -699,7 +632,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
699
632
|
WorkstreamSchema,
|
|
700
633
|
)
|
|
701
634
|
|
|
702
|
-
return await this.toNormalizedWorkstreams(workstreams)
|
|
635
|
+
return await this.toNormalizedWorkstreams(workstreams, { checkLease: false })
|
|
703
636
|
}
|
|
704
637
|
|
|
705
638
|
private normalizeWorkstreamId(id: unknown): string {
|
|
@@ -729,7 +662,10 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
729
662
|
return WORKSTREAM.DEFAULT_TITLE
|
|
730
663
|
}
|
|
731
664
|
|
|
732
|
-
private async computeIsRunning(
|
|
665
|
+
private async computeIsRunning(
|
|
666
|
+
workstream: Pick<WorkstreamRecord, 'id' | 'activeRunId'>,
|
|
667
|
+
options: { checkLease: boolean },
|
|
668
|
+
): Promise<boolean> {
|
|
733
669
|
const activeRunId =
|
|
734
670
|
typeof workstream.activeRunId === 'string' && workstream.activeRunId.trim().length > 0
|
|
735
671
|
? workstream.activeRunId
|
|
@@ -743,17 +679,24 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
743
679
|
return true
|
|
744
680
|
}
|
|
745
681
|
|
|
682
|
+
if (!options.checkLease) {
|
|
683
|
+
return true
|
|
684
|
+
}
|
|
685
|
+
|
|
746
686
|
return await this.hasActiveRunLease(ensureRecordId(workstream.id, TABLES.WORKSTREAM))
|
|
747
687
|
}
|
|
748
688
|
|
|
749
|
-
private async toNormalizedWorkstream(
|
|
750
|
-
|
|
689
|
+
private async toNormalizedWorkstream(
|
|
690
|
+
workstream: WorkstreamRecord,
|
|
691
|
+
options: { checkLease?: boolean } = {},
|
|
692
|
+
): Promise<NormalizedWorkstream> {
|
|
693
|
+
const isRunning = await this.computeIsRunning(workstream, { checkLease: options.checkLease ?? true })
|
|
751
694
|
const isCompacting = workstream.isCompacting === true
|
|
752
695
|
const mode = workstream.mode
|
|
753
696
|
const core = workstream.core
|
|
754
697
|
const coreType = core && typeof workstream.coreType === 'string' ? workstream.coreType : undefined
|
|
755
698
|
const status = workstream.status
|
|
756
|
-
return {
|
|
699
|
+
return NormalizedWorkstreamSchema.parse({
|
|
757
700
|
id: this.normalizeWorkstreamId(workstream.id),
|
|
758
701
|
userId: this.normalizeRecordIdString(workstream.userId, TABLES.USER),
|
|
759
702
|
organizationId: this.normalizeRecordIdString(workstream.organizationId, TABLES.ORGANIZATION),
|
|
@@ -769,28 +712,26 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
769
712
|
memoryBlock: this.formatMemoryBlockForPrompt(workstream),
|
|
770
713
|
createdAt: toIsoDateTimeString(workstream.createdAt),
|
|
771
714
|
updatedAt: toIsoDateTimeString(workstream.updatedAt),
|
|
772
|
-
}
|
|
715
|
+
})
|
|
773
716
|
}
|
|
774
717
|
|
|
775
|
-
private async toNormalizedWorkstreams(
|
|
776
|
-
|
|
718
|
+
private async toNormalizedWorkstreams(
|
|
719
|
+
workstreams: WorkstreamRecord[],
|
|
720
|
+
options: { checkLease?: boolean } = {},
|
|
721
|
+
): Promise<NormalizedWorkstream[]> {
|
|
722
|
+
return await Promise.all(
|
|
723
|
+
workstreams.map(async (workstream) => await this.toNormalizedWorkstream(workstream, options)),
|
|
724
|
+
)
|
|
777
725
|
}
|
|
778
726
|
|
|
779
|
-
toPublicWorkstream(workstream: NormalizedWorkstream) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
...
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
status: workstream.status,
|
|
788
|
-
nameGenerated: workstream.nameGenerated,
|
|
789
|
-
isRunning: workstream.isRunning,
|
|
790
|
-
isCompacting: workstream.isCompacting,
|
|
791
|
-
createdAt: workstream.createdAt,
|
|
792
|
-
updatedAt: workstream.updatedAt,
|
|
793
|
-
}
|
|
727
|
+
toPublicWorkstream(workstream: NormalizedWorkstream): PublicWorkstream {
|
|
728
|
+
const {
|
|
729
|
+
organizationId: _organizationId,
|
|
730
|
+
userId: _userId,
|
|
731
|
+
memoryBlock: _memoryBlock,
|
|
732
|
+
...publicWorkstream
|
|
733
|
+
} = workstream
|
|
734
|
+
return PublicWorkstreamSchema.parse(publicWorkstream)
|
|
794
735
|
}
|
|
795
736
|
|
|
796
737
|
async incrementTurnCount(workstreamId: RecordIdRef): Promise<number> {
|
|
@@ -803,10 +744,6 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
803
744
|
return result[0].turnCount
|
|
804
745
|
}
|
|
805
746
|
|
|
806
|
-
async persistGeneratedTitle(workstreamId: RecordIdRef, title: string): Promise<void> {
|
|
807
|
-
await this.update(workstreamId, { title, nameGenerated: true })
|
|
808
|
-
}
|
|
809
|
-
|
|
810
747
|
private assertMutableWorkstream(
|
|
811
748
|
workstream: WorkstreamRecord,
|
|
812
749
|
action: 'rename' | 'archive' | 'unarchive' | 'delete',
|
|
@@ -818,6 +755,39 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
|
|
|
818
755
|
throw new Error(`Core workstreams cannot be ${action}d`)
|
|
819
756
|
}
|
|
820
757
|
}
|
|
758
|
+
|
|
759
|
+
private async upsertDeterministicWorkstream(
|
|
760
|
+
workstreamId: RecordIdRef,
|
|
761
|
+
data: Record<string, unknown>,
|
|
762
|
+
): Promise<WorkstreamRecord> {
|
|
763
|
+
const workstreamRef = ensureRecordId(workstreamId, TABLES.WORKSTREAM)
|
|
764
|
+
const existing = await this.findById(workstreamRef)
|
|
765
|
+
if (existing) {
|
|
766
|
+
return existing
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
let createError: unknown = null
|
|
770
|
+
let workstream = await databaseService
|
|
771
|
+
.createWithId(TABLES.WORKSTREAM, workstreamRef, data, WorkstreamSchema)
|
|
772
|
+
.catch((error) => {
|
|
773
|
+
createError = error
|
|
774
|
+
return null
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
if (!workstream) {
|
|
778
|
+
workstream = await this.findById(workstreamRef)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (workstream) {
|
|
782
|
+
return workstream
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (createError instanceof Error) {
|
|
786
|
+
throw createError
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
throw new Error('Failed to create or load deterministic workstream')
|
|
790
|
+
}
|
|
821
791
|
}
|
|
822
792
|
|
|
823
793
|
export const workstreamService = new WorkstreamService()
|
|
@@ -1,49 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sdkPublicWorkstreamSchema, sdkWorkstreamRecordSchema, sdkWorkstreamSchema } from '@lota-sdk/shared'
|
|
2
|
+
import type { SdkPublicWorkstream, SdkWorkstreamRecord } from '@lota-sdk/shared'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
|
|
5
|
+
export const WorkstreamSchema = sdkWorkstreamRecordSchema
|
|
6
|
+
export type WorkstreamRecord = SdkWorkstreamRecord
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
mode: 'direct' | 'group'
|
|
12
|
-
core: boolean
|
|
13
|
-
coreType?: string
|
|
14
|
-
nameGenerated: boolean // Ideally `isNameGenerated`, but maps directly to SurrealDB column `nameGenerated`
|
|
15
|
-
isRunning: boolean
|
|
16
|
-
isCompacting: boolean
|
|
17
|
-
agentId?: string | null
|
|
18
|
-
memoryBlock: string
|
|
19
|
-
createdAt: string
|
|
20
|
-
updatedAt: string
|
|
21
|
-
userId: string
|
|
22
|
-
organizationId: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const WorkstreamSchema = z.object({
|
|
26
|
-
id: recordIdSchema,
|
|
27
|
-
mode: WorkstreamModeSchema,
|
|
28
|
-
core: z.boolean(),
|
|
29
|
-
coreType: CoreWorkstreamTypeSchema.nullish(),
|
|
30
|
-
agentId: z.string().nullish(),
|
|
31
|
-
title: z.string().nullish(),
|
|
32
|
-
status: sdkWorkstreamStatusSchema,
|
|
33
|
-
memoryBlock: z.string().nullish(),
|
|
34
|
-
memoryBlockSummary: z.string().nullish(),
|
|
35
|
-
activeRunId: z.string().nullish(),
|
|
36
|
-
activeStreamId: z.string().nullish(),
|
|
37
|
-
compactionSummary: z.string().nullish(),
|
|
38
|
-
lastCompactedMessageId: z.string().nullish(),
|
|
39
|
-
nameGenerated: z.boolean(), // Ideally `isNameGenerated`, but maps directly to SurrealDB column `nameGenerated`
|
|
40
|
-
isCompacting: z.boolean().optional(),
|
|
41
|
-
state: z.unknown().optional(),
|
|
42
|
-
turnCount: z.number().int(),
|
|
43
|
-
createdAt: z.coerce.date(),
|
|
44
|
-
updatedAt: z.coerce.date(),
|
|
45
|
-
userId: recordIdSchema,
|
|
46
|
-
organizationId: recordIdSchema,
|
|
8
|
+
export const NormalizedWorkstreamSchema = sdkWorkstreamSchema.extend({
|
|
9
|
+
agentId: z.string().optional(),
|
|
10
|
+
coreType: z.string().optional(),
|
|
11
|
+
memoryBlock: z.string(),
|
|
47
12
|
})
|
|
48
13
|
|
|
49
|
-
export type
|
|
14
|
+
export type NormalizedWorkstream = z.infer<typeof NormalizedWorkstreamSchema>
|
|
15
|
+
export type PublicWorkstream = SdkPublicWorkstream
|
|
16
|
+
|
|
17
|
+
export { sdkPublicWorkstreamSchema as PublicWorkstreamSchema }
|
|
@@ -52,6 +52,9 @@ produce one updated workspace profile summary plus durable memory facts.
|
|
|
52
52
|
- Set type to one of: fact, preference, decision.
|
|
53
53
|
- Set confidence between 0 and 1.
|
|
54
54
|
- Set durability to core, standard, or ephemeral based on expected longevity.
|
|
55
|
+
- Set importance between 0 and 1 for long-term usefulness.
|
|
56
|
+
- Set classification to durable, transient, or uncertain.
|
|
57
|
+
- Set rationale to one short evidence-grounded sentence.
|
|
55
58
|
</facts-format>
|
|
56
59
|
|
|
57
60
|
<output-contract>
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ResumeExecutionPlanRunArgsSchema,
|
|
6
6
|
ReplaceExecutionPlanArgsSchema,
|
|
7
7
|
SubmitExecutionNodeResultArgsSchema,
|
|
8
|
+
expandAgentPlanDraft,
|
|
8
9
|
getLatestExecutionPlanResult,
|
|
9
10
|
} from '@lota-sdk/shared'
|
|
10
11
|
import { tool } from 'ai'
|
|
@@ -17,17 +18,21 @@ export function createCreateExecutionPlanTool(params: {
|
|
|
17
18
|
workstreamId: RecordIdRef
|
|
18
19
|
agentId: string
|
|
19
20
|
onPlanChanged?: () => void
|
|
21
|
+
validateInlinePlan?: (draft: ReturnType<typeof expandAgentPlanDraft>) => void
|
|
20
22
|
}) {
|
|
21
23
|
return tool({
|
|
22
24
|
description:
|
|
23
|
-
'Create a contract-driven execution plan for this workstream.
|
|
25
|
+
'Create a contract-driven execution plan for this workstream. Use createProjectWithPlan instead when the work needs a dedicated project workstream or a larger 3+ node plan.',
|
|
24
26
|
inputSchema: CreateExecutionPlanArgsSchema,
|
|
25
27
|
execute: async (input) => {
|
|
28
|
+
const { targetWorkstreamId, ...draftInput } = input
|
|
29
|
+
const draft = expandAgentPlanDraft(draftInput)
|
|
30
|
+
params.validateInlinePlan?.(draft)
|
|
26
31
|
const result = await executionPlanService.createPlan({
|
|
27
32
|
organizationId: params.orgId,
|
|
28
|
-
workstreamId: params.workstreamId,
|
|
33
|
+
workstreamId: targetWorkstreamId ?? params.workstreamId,
|
|
29
34
|
leadAgentId: params.agentId,
|
|
30
|
-
input,
|
|
35
|
+
input: draft,
|
|
31
36
|
})
|
|
32
37
|
params.onPlanChanged?.()
|
|
33
38
|
return result
|
|
@@ -46,11 +51,12 @@ export function createReplaceExecutionPlanTool(params: {
|
|
|
46
51
|
'Replace the active execution run with a new contract-driven plan when the graph, contracts, or approach materially change.',
|
|
47
52
|
inputSchema: ReplaceExecutionPlanArgsSchema,
|
|
48
53
|
execute: async (input) => {
|
|
54
|
+
const { runId, reason, ...draftInput } = input
|
|
49
55
|
const result = await executionPlanService.replacePlan({
|
|
50
56
|
organizationId: params.orgId,
|
|
51
57
|
workstreamId: params.workstreamId,
|
|
52
58
|
leadAgentId: params.agentId,
|
|
53
|
-
input,
|
|
59
|
+
input: { runId, reason, ...expandAgentPlanDraft(draftInput) },
|
|
54
60
|
})
|
|
55
61
|
params.onPlanChanged?.()
|
|
56
62
|
return result
|
|
@@ -95,8 +101,7 @@ export function createListExecutionPlansTool(params: { workstreamId: RecordIdRef
|
|
|
95
101
|
|
|
96
102
|
export function createGetExecutionPlanDetailsTool(params: { workstreamId: RecordIdRef }) {
|
|
97
103
|
return tool({
|
|
98
|
-
description:
|
|
99
|
-
'Load a specific execution run by runId, or the most recent active run if runId is omitted. Returns full graph state, node contracts, events, approvals, artifacts, and checkpoints.',
|
|
104
|
+
description: 'Load a plan run and its current state.',
|
|
100
105
|
inputSchema: GetActiveExecutionPlanArgsSchema,
|
|
101
106
|
execute: async (input) =>
|
|
102
107
|
await executionPlanService.getActivePlanToolResult({
|
package/src/tools/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './execution-plan.tool'
|
|
2
2
|
export * from './fetch-webpage.tool'
|
|
3
3
|
export * from './memory-block.tool'
|
|
4
|
+
export * from './project-with-plan.tool'
|
|
4
5
|
export * from './read-file-parts.tool'
|
|
5
6
|
export * from './remember-memory.tool'
|
|
6
7
|
export * from './research-topic.tool'
|