@lota-sdk/core 0.4.8 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,80 +0,0 @@
1
- import type { PlanNodeOwner, PlanNodeType } from '@lota-sdk/shared'
2
- import { PlanEventSchema } from '@lota-sdk/shared'
3
-
4
- import { aiLogger } from '../config/logger'
5
- import { ensureRecordId } from '../db/record-id'
6
- import { databaseService } from '../db/service'
7
- import { TABLES } from '../db/tables'
8
- import { feedbackLoopService } from './feedback-loop.service'
9
- import { institutionalMemoryService } from './institutional-memory.service'
10
- import { planEventDeliveryService } from './plan-event-delivery.service'
11
- import { planRunService } from './plan-run.service'
12
- import type { PlanValidationIssueInput } from './plan-validator.service'
13
- import { qualityMetricsService } from './quality-metrics.service'
14
-
15
- export async function runPlanNodeCompletionSideEffects(params: {
16
- runId: string
17
- organizationId: string
18
- nodeId: string
19
- nodeLabel: string
20
- nodeOwnerRef: string
21
- nodeOwnerType: PlanNodeOwner['executorType']
22
- nodeType: PlanNodeType
23
- nodeStartedAt?: string | Date | null
24
- nodeAttemptCount: number
25
- artifactCount: number
26
- validationIssues: PlanValidationIssueInput[]
27
- }): Promise<void> {
28
- const executionTimeMs = params.nodeStartedAt ? Date.now() - new Date(params.nodeStartedAt).getTime() : 0
29
- await qualityMetricsService.recordNodeMetrics({
30
- organizationId: params.organizationId,
31
- runId: params.runId,
32
- nodeId: params.nodeId,
33
- metrics: {
34
- executionTimeMs: Math.max(0, executionTimeMs),
35
- attemptCount: params.nodeAttemptCount,
36
- artifactCount: params.artifactCount,
37
- validationIssueCount: params.validationIssues.length,
38
- ownerRef: params.nodeOwnerRef,
39
- ownerType: params.nodeOwnerType,
40
- nodeType: params.nodeType,
41
- },
42
- })
43
- }
44
-
45
- async function runPlanCompletionSideEffects(params: { runId: string; organizationId: string }): Promise<void> {
46
- await qualityMetricsService.recordCycleMetrics({ organizationId: params.organizationId, runId: params.runId })
47
-
48
- const recommendations = await feedbackLoopService.analyzeOutcomes({
49
- runId: params.runId,
50
- organizationId: params.organizationId,
51
- })
52
- if (recommendations.length > 0) {
53
- const run = await planRunService.getRunById(params.runId)
54
- const specRecord = await planRunService.getPlanSpecById(run.planSpecId)
55
- const event = await databaseService.create(
56
- TABLES.PLAN_EVENT,
57
- {
58
- planSpecId: ensureRecordId(specRecord.id, TABLES.PLAN_SPEC),
59
- runId: ensureRecordId(run.id, TABLES.PLAN_RUN),
60
- eventType: 'feedback-analyzed',
61
- message: `Feedback analysis produced ${recommendations.length} recommendation(s).`,
62
- detail: { recommendations },
63
- emittedBy: 'system',
64
- },
65
- PlanEventSchema,
66
- )
67
- await planEventDeliveryService.dispatchEvent(event)
68
- }
69
-
70
- await institutionalMemoryService.extractPatterns({ organizationId: params.organizationId, runId: params.runId })
71
- }
72
-
73
- export async function runPlanCompletionSideEffectsSafely(params: {
74
- runId: string
75
- organizationId: string
76
- }): Promise<void> {
77
- await runPlanCompletionSideEffects(params).catch((error) => {
78
- aiLogger.warn`Plan completion side effects failed for run ${params.runId}: ${error instanceof Error ? error.message : String(error)}`
79
- })
80
- }
@@ -1,157 +0,0 @@
1
- import type { PlanArtifactRecord, PlanDependency } from '@lota-sdk/shared'
2
-
3
- import { serverLogger } from '../config/logger'
4
- import { recordIdToString } from '../db/record-id'
5
- import { TABLES } from '../db/tables'
6
- import type { PlanValidationIssueInput } from './plan-validator.service'
7
-
8
- export interface DependencyResolutionResult {
9
- resolved: Map<string, PlanArtifactRecord>
10
- unresolved: PlanDependency[]
11
- notifications: Array<{ dependency: PlanDependency; reason: string }>
12
- }
13
-
14
- class PlanCoordinationService {
15
- /**
16
- * Resolve cross-plan artifact dependencies.
17
- * For each dependency:
18
- * 1. Find the source plan by spec id in the thread
19
- * 2. Find the artifact by (nodeId, artifactName) in that plan's run
20
- * 3. Check staleness if maxStalenessMs set
21
- * 4. Based on triggerMode:
22
- * 'block' -> unresolved if missing/stale
23
- * 'notify' -> proceed but record notification
24
- * 'best-effort' -> proceed regardless, no notification
25
- */
26
- async resolveDependencies(params: {
27
- dependencies: PlanDependency[]
28
- threadId: string
29
- }): Promise<DependencyResolutionResult> {
30
- const { planRunService } = await import('./plan-run.service')
31
-
32
- const resolved = new Map<string, PlanArtifactRecord>()
33
- const unresolved: PlanDependency[] = []
34
- const notifications: DependencyResolutionResult['notifications'] = []
35
- const specs = await planRunService.listPlanSpecsByThread(params.threadId)
36
-
37
- for (const dep of params.dependencies) {
38
- const depKey = `${dep.sourcePlanSpecId}:${dep.sourceNodeId}:${dep.artifactName}`
39
-
40
- const sourceSpec = specs.find((s) => recordIdToString(s.id, TABLES.PLAN_SPEC) === dep.sourcePlanSpecId)
41
- if (!sourceSpec) {
42
- const reason = `Source plan "${dep.sourcePlanSpecId}" not found in thread.`
43
- if (dep.triggerMode === 'block') {
44
- unresolved.push(dep)
45
- } else if (dep.triggerMode === 'notify') {
46
- notifications.push({ dependency: dep, reason })
47
- serverLogger.warn`Dependency unmet (notify): ${reason}`
48
- }
49
- // best-effort: silently proceed
50
- continue
51
- }
52
-
53
- const runs = await planRunService.listRunsBySpec(sourceSpec.id)
54
- const activeRun = runs.find((r) => r.status === 'completed' || r.status === 'running')
55
- if (!activeRun) {
56
- const reason = `No active run found for plan "${sourceSpec.title}".`
57
- if (dep.triggerMode === 'block') {
58
- unresolved.push(dep)
59
- } else if (dep.triggerMode === 'notify') {
60
- notifications.push({ dependency: dep, reason })
61
- serverLogger.warn`Dependency unmet (notify): ${reason}`
62
- }
63
- continue
64
- }
65
-
66
- const artifacts = await planRunService.listArtifacts(activeRun.id)
67
- const artifact = artifacts.find((a) => a.nodeId === dep.sourceNodeId && a.name === dep.artifactName)
68
-
69
- if (!artifact) {
70
- const reason = `Artifact "${dep.artifactName}" not found on node "${dep.sourceNodeId}" in plan "${sourceSpec.title}".`
71
- if (dep.triggerMode === 'block') {
72
- unresolved.push(dep)
73
- } else if (dep.triggerMode === 'notify') {
74
- notifications.push({ dependency: dep, reason })
75
- serverLogger.warn`Dependency unmet (notify): ${reason}`
76
- }
77
- continue
78
- }
79
-
80
- if (dep.maxStalenessMs && this.isStale(artifact, dep.maxStalenessMs)) {
81
- const reason = `Artifact "${dep.artifactName}" from plan "${sourceSpec.title}" is stale.`
82
- if (dep.triggerMode === 'block') {
83
- unresolved.push(dep)
84
- continue
85
- }
86
- if (dep.triggerMode === 'notify') {
87
- notifications.push({ dependency: dep, reason })
88
- serverLogger.warn`Dependency stale (notify): ${reason}`
89
- }
90
- // best-effort and notify: use stale artifact anyway
91
- }
92
-
93
- resolved.set(depKey, artifact)
94
- }
95
-
96
- return { resolved, unresolved, notifications }
97
- }
98
-
99
- /** Check if an artifact has exceeded the staleness window. */
100
- isStale(artifact: Pick<PlanArtifactRecord, 'createdAt'>, maxStalenessMs: number): boolean {
101
- if (!maxStalenessMs) return false
102
- return Date.now() - artifact.createdAt.getTime() > maxStalenessMs
103
- }
104
-
105
- /**
106
- * Validate no circular dependencies exist using Kahn's algorithm.
107
- * Build adjacency: planSpecId -> depends on upstream planSpecIds
108
- * Run topological sort; if not all visited -> cycle exists.
109
- */
110
- validateNoCycles(
111
- specs: Array<{ id: string; title?: string; dependencies?: PlanDependency[] }>,
112
- ): PlanValidationIssueInput[] {
113
- const adj = new Map<string, Set<string>>()
114
- const inDegree = new Map<string, number>()
115
- const labels = new Map(specs.map((spec) => [spec.id, spec.title ?? spec.id]))
116
-
117
- for (const spec of specs) {
118
- if (!adj.has(spec.id)) adj.set(spec.id, new Set())
119
- if (!inDegree.has(spec.id)) inDegree.set(spec.id, 0)
120
-
121
- for (const dep of spec.dependencies ?? []) {
122
- if (!adj.has(dep.sourcePlanSpecId)) adj.set(dep.sourcePlanSpecId, new Set())
123
- if (!inDegree.has(dep.sourcePlanSpecId)) inDegree.set(dep.sourcePlanSpecId, 0)
124
-
125
- adj.get(dep.sourcePlanSpecId)?.add(spec.id)
126
- inDegree.set(spec.id, (inDegree.get(spec.id) ?? 0) + 1)
127
- }
128
- }
129
-
130
- const queue = [...inDegree.entries()].filter(([, d]) => d === 0).map(([t]) => t)
131
- const visited = new Set<string>()
132
-
133
- while (queue.length > 0) {
134
- const node = queue.shift()
135
- if (!node) break
136
- visited.add(node)
137
- for (const dep of adj.get(node) ?? []) {
138
- const d = (inDegree.get(dep) ?? 0) - 1
139
- inDegree.set(dep, d)
140
- if (d === 0) queue.push(dep)
141
- }
142
- }
143
-
144
- const unvisited = specs.filter((s) => !visited.has(s.id))
145
- if (unvisited.length === 0) return []
146
-
147
- return [
148
- {
149
- severity: 'blocking',
150
- code: 'circular_dependency',
151
- message: `Circular plan dependencies detected involving: ${unvisited.map((s) => labels.get(s.id) ?? s.id).join(', ')}`,
152
- },
153
- ]
154
- }
155
- }
156
-
157
- export const planCoordinationService = new PlanCoordinationService()
@@ -1,284 +0,0 @@
1
- import { PlanArtifactSchema, PlanCycleRecordSchema, PlanRunSchema } from '@lota-sdk/shared'
2
- import type {
3
- CarryForwardPolicy,
4
- CycleSchedule,
5
- PlanArtifactRecord,
6
- PlanCycleRecord,
7
- PlanDraft,
8
- PlanRunStatus,
9
- PlanScheduleSpec,
10
- PlanTemplateRecord,
11
- } from '@lota-sdk/shared'
12
-
13
- import type { RecordIdInput } from '../db/record-id'
14
- import { ensureRecordId, recordIdToString } from '../db/record-id'
15
- import { databaseService } from '../db/service'
16
- import { TABLES } from '../db/tables'
17
- import { planSchedulerService } from './plan-scheduler.service'
18
- import { planTemplateService } from './plan-template.service'
19
-
20
- const TERMINAL_RUN_STATUSES: ReadonlySet<PlanRunStatus> = new Set(['completed', 'failed', 'aborted'])
21
-
22
- function cycleScheduleToSpec(schedule: CycleSchedule): PlanScheduleSpec {
23
- const startAt = schedule.startAt ? new Date(schedule.startAt) : undefined
24
- const isFutureStart = startAt && startAt.getTime() > Date.now()
25
-
26
- if (isFutureStart) {
27
- return { type: 'absolute', at: startAt.toISOString(), missedPolicy: 'run-immediately' }
28
- }
29
-
30
- const cron = frequencyToCron(schedule.frequency, schedule.customIntervalMs)
31
- if (cron) {
32
- return { type: 'cron', cron, missedPolicy: 'run-immediately' }
33
- }
34
-
35
- if (schedule.frequency === 'biweekly') {
36
- return { type: 'monitoring', intervalMs: 14 * 24 * 60 * 60 * 1000, missedPolicy: 'run-immediately' }
37
- }
38
-
39
- if (schedule.frequency === 'custom' && schedule.customIntervalMs) {
40
- return { type: 'monitoring', intervalMs: schedule.customIntervalMs, missedPolicy: 'run-immediately' }
41
- }
42
-
43
- return { type: 'cron', cron: '0 0 * * *', missedPolicy: 'run-immediately' }
44
- }
45
-
46
- function frequencyToCron(frequency: CycleSchedule['frequency'], _customIntervalMs?: number): string | null {
47
- switch (frequency) {
48
- case 'daily':
49
- return '0 0 * * *'
50
- case 'weekly':
51
- return '0 0 * * 1'
52
- case 'monthly':
53
- return '0 0 1 * *'
54
- case 'quarterly':
55
- return '0 0 1 */3 *'
56
- case 'biweekly':
57
- case 'custom':
58
- return null
59
- }
60
- }
61
-
62
- class PlanCycleService {
63
- cycleScheduleToSpec = cycleScheduleToSpec
64
-
65
- async createCycle(params: {
66
- organizationId: RecordIdInput
67
- threadId: RecordIdInput
68
- templateId: RecordIdInput
69
- name: string
70
- schedule: CycleSchedule
71
- carryForwardPolicy?: CarryForwardPolicy
72
- leadAgentId: string
73
- }): Promise<PlanCycleRecord> {
74
- const now = new Date()
75
-
76
- const cycle = await databaseService.create(
77
- TABLES.PLAN_CYCLE,
78
- {
79
- organizationId: ensureRecordId(params.organizationId, TABLES.ORGANIZATION),
80
- threadId: ensureRecordId(params.threadId, TABLES.THREAD),
81
- templateId: ensureRecordId(params.templateId, TABLES.PLAN_TEMPLATE),
82
- name: params.name,
83
- schedule: params.schedule,
84
- carryForwardPolicy: params.carryForwardPolicy ?? 'incomplete-only',
85
- status: 'active',
86
- currentIteration: 0,
87
- createdAt: now,
88
- },
89
- PlanCycleRecordSchema,
90
- )
91
-
92
- const createResult = await planTemplateService.instantiate({
93
- templateId: params.templateId,
94
- organizationId: params.organizationId,
95
- threadId: params.threadId,
96
- leadAgentId: params.leadAgentId,
97
- })
98
-
99
- const createdRunId = createResult.plan?.runId
100
-
101
- const scheduleSpec = cycleScheduleToSpec(params.schedule)
102
- const scheduleRecord = await planSchedulerService.createSchedule({
103
- organizationId: params.organizationId,
104
- threadId: params.threadId,
105
- ...(createdRunId ? { runId: ensureRecordId(createdRunId, TABLES.PLAN_RUN) } : {}),
106
- scheduleSpec,
107
- })
108
-
109
- const updated = await databaseService.update(
110
- TABLES.PLAN_CYCLE,
111
- ensureRecordId(cycle.id, TABLES.PLAN_CYCLE),
112
- {
113
- scheduleId: ensureRecordId(scheduleRecord.id, TABLES.PLAN_SCHEDULE),
114
- ...(createdRunId ? { currentRunId: ensureRecordId(createdRunId, TABLES.PLAN_RUN) } : {}),
115
- currentIteration: 1,
116
- },
117
- PlanCycleRecordSchema,
118
- )
119
-
120
- return updated ?? cycle
121
- }
122
-
123
- async advanceCycle(cycleId: RecordIdInput): Promise<void> {
124
- const cycle = await this.getCycle(cycleId)
125
- if (!cycle) {
126
- throw new Error(`Cycle not found: ${recordIdToString(cycleId, TABLES.PLAN_CYCLE)}`)
127
- }
128
-
129
- if (cycle.status !== 'active') {
130
- return
131
- }
132
-
133
- if (cycle.currentRunId) {
134
- const runRecord = await databaseService.findOne(
135
- TABLES.PLAN_RUN,
136
- { id: ensureRecordId(cycle.currentRunId, TABLES.PLAN_RUN) },
137
- PlanRunSchema,
138
- )
139
- if (runRecord && !TERMINAL_RUN_STATUSES.has(runRecord.status)) {
140
- return
141
- }
142
- }
143
-
144
- const template = await planTemplateService.getTemplate(cycle.templateId)
145
- if (!template) {
146
- throw new Error(`Template not found for cycle: ${recordIdToString(cycle.templateId, TABLES.PLAN_TEMPLATE)}`)
147
- }
148
-
149
- const previousRunArtifacts = cycle.currentRunId
150
- ? await databaseService.findMany(
151
- TABLES.PLAN_ARTIFACT,
152
- { runId: ensureRecordId(cycle.currentRunId, TABLES.PLAN_RUN) },
153
- PlanArtifactSchema,
154
- { orderBy: 'createdAt', orderDir: 'ASC' },
155
- )
156
- : []
157
-
158
- const draft = this.buildCarryForwardDraft({ template, previousRunArtifacts, policy: cycle.carryForwardPolicy })
159
-
160
- const result = await planTemplateService.instantiate({
161
- templateId: cycle.templateId,
162
- organizationId: cycle.organizationId,
163
- threadId: cycle.threadId,
164
- leadAgentId: 'system',
165
- overrides: draft,
166
- })
167
-
168
- const newRunId = result.plan?.runId
169
-
170
- await databaseService.update(
171
- TABLES.PLAN_CYCLE,
172
- ensureRecordId(cycleId, TABLES.PLAN_CYCLE),
173
- {
174
- ...(newRunId ? { currentRunId: ensureRecordId(newRunId, TABLES.PLAN_RUN) } : {}),
175
- currentIteration: cycle.currentIteration + 1,
176
- },
177
- PlanCycleRecordSchema,
178
- )
179
-
180
- // TODO: re-enable when playbook records exist and IDs align
181
- // The previous implementation passed a PLAN_TEMPLATE record ID as a PLAYBOOK ID,
182
- // causing refineFromCycle to always fail silently.
183
- }
184
-
185
- buildCarryForwardDraft(params: {
186
- template: PlanTemplateRecord
187
- previousRunArtifacts: PlanArtifactRecord[]
188
- policy: CarryForwardPolicy
189
- }): PlanDraft {
190
- const draft = { ...params.template.draft }
191
-
192
- if (params.policy === 'none' || params.previousRunArtifacts.length === 0) {
193
- return draft
194
- }
195
-
196
- let artifacts: PlanArtifactRecord[]
197
- if (params.policy === 'incomplete-only') {
198
- artifacts = params.previousRunArtifacts.filter((a) => a.kind === 'markdown' || a.kind === 'json')
199
- } else {
200
- // 'all-pending'
201
- artifacts = [...params.previousRunArtifacts]
202
- }
203
-
204
- if (artifacts.length > 0) {
205
- const carryContext = artifacts.map((a) => `[carry-forward] ${a.name}: ${a.pointer}`)
206
- draft.objective = `${draft.objective}\n\nCarry-forward context:\n${carryContext.join('\n')}`
207
- }
208
-
209
- return draft
210
- }
211
-
212
- async cancelCycle(cycleId: RecordIdInput): Promise<void> {
213
- const cycle = await this.getCycle(cycleId)
214
- if (!cycle) {
215
- throw new Error(`Cycle not found: ${recordIdToString(cycleId, TABLES.PLAN_CYCLE)}`)
216
- }
217
-
218
- if (cycle.scheduleId) {
219
- await planSchedulerService.cancelSchedule(cycle.scheduleId)
220
- }
221
-
222
- await databaseService.update(
223
- TABLES.PLAN_CYCLE,
224
- ensureRecordId(cycleId, TABLES.PLAN_CYCLE),
225
- { status: 'cancelled' },
226
- PlanCycleRecordSchema,
227
- )
228
- }
229
-
230
- async pauseCycle(cycleId: RecordIdInput): Promise<void> {
231
- const cycle = await this.getCycle(cycleId)
232
- if (!cycle) {
233
- throw new Error(`Cycle not found: ${recordIdToString(cycleId, TABLES.PLAN_CYCLE)}`)
234
- }
235
-
236
- if (cycle.scheduleId) {
237
- await planSchedulerService.pauseSchedule(cycle.scheduleId)
238
- }
239
-
240
- await databaseService.update(
241
- TABLES.PLAN_CYCLE,
242
- ensureRecordId(cycleId, TABLES.PLAN_CYCLE),
243
- { status: 'paused' },
244
- PlanCycleRecordSchema,
245
- )
246
- }
247
-
248
- async resumeCycle(cycleId: RecordIdInput): Promise<void> {
249
- const cycle = await this.getCycle(cycleId)
250
- if (!cycle) {
251
- throw new Error(`Cycle not found: ${recordIdToString(cycleId, TABLES.PLAN_CYCLE)}`)
252
- }
253
-
254
- if (cycle.scheduleId) {
255
- await planSchedulerService.resumeSchedule(cycle.scheduleId)
256
- }
257
-
258
- await databaseService.update(
259
- TABLES.PLAN_CYCLE,
260
- ensureRecordId(cycleId, TABLES.PLAN_CYCLE),
261
- { status: 'active' },
262
- PlanCycleRecordSchema,
263
- )
264
- }
265
-
266
- async listCycles(threadId: RecordIdInput): Promise<PlanCycleRecord[]> {
267
- return databaseService.findMany(
268
- TABLES.PLAN_CYCLE,
269
- { threadId: ensureRecordId(threadId, TABLES.THREAD) },
270
- PlanCycleRecordSchema,
271
- { orderBy: 'createdAt', orderDir: 'ASC' },
272
- )
273
- }
274
-
275
- async getCycle(cycleId: RecordIdInput): Promise<PlanCycleRecord | null> {
276
- return databaseService.findOne(
277
- TABLES.PLAN_CYCLE,
278
- { id: ensureRecordId(cycleId, TABLES.PLAN_CYCLE) },
279
- PlanCycleRecordSchema,
280
- )
281
- }
282
- }
283
-
284
- export const planCycleService = new PlanCycleService()