@lota-sdk/core 0.4.7 → 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/model-constants.ts +1 -0
  9. package/src/config/thread-defaults.ts +33 -21
  10. package/src/create-runtime.ts +725 -383
  11. package/src/db/base.service.ts +52 -28
  12. package/src/db/cursor-pagination.ts +71 -30
  13. package/src/db/memory-store.helpers.ts +4 -7
  14. package/src/db/memory-store.ts +856 -598
  15. package/src/db/memory.ts +398 -275
  16. package/src/db/record-id.ts +32 -10
  17. package/src/db/schema-fingerprint.ts +30 -12
  18. package/src/db/service-normalization.ts +255 -0
  19. package/src/db/service.ts +726 -761
  20. package/src/db/startup.ts +140 -66
  21. package/src/db/transaction-conflict.ts +15 -0
  22. package/src/effect/awaitable-effect.ts +87 -0
  23. package/src/effect/errors.ts +121 -0
  24. package/src/effect/helpers.ts +98 -0
  25. package/src/effect/index.ts +22 -0
  26. package/src/effect/layers.ts +228 -0
  27. package/src/effect/runtime-ref.ts +25 -0
  28. package/src/effect/runtime.ts +31 -0
  29. package/src/effect/services.ts +57 -0
  30. package/src/effect/zod.ts +43 -0
  31. package/src/embeddings/provider.ts +122 -71
  32. package/src/index.ts +46 -1
  33. package/src/openrouter/direct-provider.ts +29 -0
  34. package/src/queues/autonomous-job.queue.ts +130 -74
  35. package/src/queues/context-compaction.queue.ts +60 -15
  36. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  37. package/src/queues/document-processor.queue.ts +52 -77
  38. package/src/queues/memory-consolidation.queue.ts +47 -32
  39. package/src/queues/organization-learning.queue.ts +13 -4
  40. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  41. package/src/queues/plan-scheduler.queue.ts +107 -31
  42. package/src/queues/post-chat-memory.queue.ts +66 -24
  43. package/src/queues/queue-factory.ts +142 -52
  44. package/src/queues/standalone-worker.ts +39 -0
  45. package/src/queues/title-generation.queue.ts +54 -9
  46. package/src/redis/connection.ts +84 -32
  47. package/src/redis/index.ts +6 -8
  48. package/src/redis/org-memory-lock.ts +60 -27
  49. package/src/redis/redis-lease-lock.ts +200 -121
  50. package/src/redis/runtime-connection.ts +10 -0
  51. package/src/redis/stream-context.ts +84 -46
  52. package/src/runtime/agent-identity-overrides.ts +2 -2
  53. package/src/runtime/agent-runtime-policy.ts +4 -1
  54. package/src/runtime/agent-stream-helpers.ts +20 -9
  55. package/src/runtime/chat-run-orchestration.ts +102 -19
  56. package/src/runtime/chat-run-registry.ts +36 -2
  57. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  58. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  59. package/src/runtime/execution-plan-visibility.ts +2 -2
  60. package/src/runtime/execution-plan.ts +42 -15
  61. package/src/runtime/graph-designer.ts +11 -7
  62. package/src/runtime/helper-model.ts +135 -48
  63. package/src/runtime/index.ts +7 -7
  64. package/src/runtime/indexed-repositories-policy.ts +3 -3
  65. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  66. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  67. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  68. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  69. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  70. package/src/runtime/plugin-resolution.ts +144 -24
  71. package/src/runtime/plugin-types.ts +9 -1
  72. package/src/runtime/post-turn-side-effects.ts +197 -130
  73. package/src/runtime/retrieval-adapters.ts +38 -4
  74. package/src/runtime/runtime-config.ts +165 -61
  75. package/src/runtime/runtime-extensions.ts +21 -34
  76. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  77. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  78. package/src/runtime/social-chat/social-chat.ts +594 -0
  79. package/src/runtime/specialist-runner.ts +36 -10
  80. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  81. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  82. package/src/runtime/thread-chat-helpers.ts +2 -2
  83. package/src/runtime/thread-plan-turn.ts +2 -1
  84. package/src/runtime/thread-turn-context.ts +172 -94
  85. package/src/runtime/turn-lifecycle.ts +93 -27
  86. package/src/services/agent-activity.service.ts +287 -203
  87. package/src/services/agent-executor.service.ts +329 -217
  88. package/src/services/artifact.service.ts +225 -148
  89. package/src/services/attachment.service.ts +137 -115
  90. package/src/services/autonomous-job.service.ts +888 -491
  91. package/src/services/chat-run-registry.service.ts +11 -1
  92. package/src/services/context-compaction.service.ts +136 -86
  93. package/src/services/document-chunk.service.ts +162 -90
  94. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  95. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  96. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  97. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  98. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  99. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  100. package/src/services/feedback-loop.service.ts +132 -76
  101. package/src/services/global-orchestrator.service.ts +80 -170
  102. package/src/services/graph-full-routing.ts +182 -0
  103. package/src/services/index.ts +18 -20
  104. package/src/services/institutional-memory.service.ts +220 -123
  105. package/src/services/learned-skill.service.ts +364 -259
  106. package/src/services/memory/memory-conversation.ts +95 -0
  107. package/src/services/memory/memory-org-memory.ts +39 -0
  108. package/src/services/memory/memory-preseeded.ts +80 -0
  109. package/src/services/memory/memory-rerank.ts +297 -0
  110. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  111. package/src/services/memory/memory.service.ts +692 -0
  112. package/src/services/memory/rerank.service.ts +209 -0
  113. package/src/services/monitoring-window.service.ts +92 -70
  114. package/src/services/mutating-approval.service.ts +62 -53
  115. package/src/services/node-workspace.service.ts +141 -98
  116. package/src/services/notification.service.ts +17 -16
  117. package/src/services/organization-member.service.ts +120 -66
  118. package/src/services/organization.service.ts +144 -51
  119. package/src/services/ownership-dispatcher.service.ts +415 -264
  120. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  121. package/src/services/plan/plan-agent-query.service.ts +322 -0
  122. package/src/services/plan/plan-approval.service.ts +102 -0
  123. package/src/services/plan/plan-artifact.service.ts +60 -0
  124. package/src/services/plan/plan-builder.service.ts +76 -0
  125. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  126. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  127. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  128. package/src/services/plan/plan-coordination.service.ts +181 -0
  129. package/src/services/plan/plan-cycle.service.ts +398 -0
  130. package/src/services/plan/plan-deadline.service.ts +547 -0
  131. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  132. package/src/services/plan/plan-executor-context.ts +35 -0
  133. package/src/services/plan/plan-executor-graph.ts +475 -0
  134. package/src/services/plan/plan-executor-helpers.ts +322 -0
  135. package/src/services/plan/plan-executor-persistence.ts +209 -0
  136. package/src/services/plan/plan-executor.service.ts +1654 -0
  137. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  138. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  139. package/src/services/plan/plan-run-serialization.ts +15 -0
  140. package/src/services/plan/plan-run.service.ts +644 -0
  141. package/src/services/plan/plan-scheduler.service.ts +385 -0
  142. package/src/services/plan/plan-template.service.ts +224 -0
  143. package/src/services/plan/plan-transaction-events.ts +33 -0
  144. package/src/services/plan/plan-validator.service.ts +907 -0
  145. package/src/services/plan/plan-workspace.service.ts +125 -0
  146. package/src/services/plugin-executor.service.ts +97 -68
  147. package/src/services/quality-metrics.service.ts +112 -94
  148. package/src/services/queue-job.service.ts +296 -230
  149. package/src/services/recent-activity-title.service.ts +65 -36
  150. package/src/services/recent-activity.service.ts +274 -259
  151. package/src/services/skill-resolver.service.ts +38 -12
  152. package/src/services/social-chat-history.service.ts +176 -125
  153. package/src/services/system-executor.service.ts +91 -61
  154. package/src/services/thread/thread-active-run.ts +203 -0
  155. package/src/services/thread/thread-bootstrap.ts +369 -0
  156. package/src/services/thread/thread-listing.ts +198 -0
  157. package/src/services/thread/thread-memory-block.ts +117 -0
  158. package/src/services/thread/thread-message.service.ts +363 -0
  159. package/src/services/thread/thread-record-store.ts +155 -0
  160. package/src/services/thread/thread-title.service.ts +74 -0
  161. package/src/services/thread/thread-turn-execution.ts +280 -0
  162. package/src/services/thread/thread-turn-message-context.ts +73 -0
  163. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  164. package/src/services/thread/thread-turn-streaming.ts +402 -0
  165. package/src/services/thread/thread-turn-tracing.ts +35 -0
  166. package/src/services/thread/thread-turn.ts +343 -0
  167. package/src/services/thread/thread.service.ts +335 -0
  168. package/src/services/user.service.ts +82 -32
  169. package/src/services/write-intent-validator.service.ts +63 -51
  170. package/src/storage/attachment-parser.ts +69 -27
  171. package/src/storage/attachment-storage.service.ts +331 -275
  172. package/src/storage/generated-document-storage.service.ts +66 -34
  173. package/src/system-agents/agent-result.ts +3 -1
  174. package/src/system-agents/context-compaction.agent.ts +2 -2
  175. package/src/system-agents/delegated-agent-factory.ts +159 -90
  176. package/src/system-agents/memory-reranker.agent.ts +2 -2
  177. package/src/system-agents/memory.agent.ts +2 -2
  178. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  179. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  180. package/src/system-agents/skill-extractor.agent.ts +2 -2
  181. package/src/system-agents/skill-manager.agent.ts +2 -2
  182. package/src/system-agents/thread-router.agent.ts +157 -113
  183. package/src/system-agents/title-generator.agent.ts +2 -2
  184. package/src/tools/execution-plan.tool.ts +220 -161
  185. package/src/tools/fetch-webpage.tool.ts +21 -17
  186. package/src/tools/firecrawl-client.ts +16 -6
  187. package/src/tools/index.ts +1 -0
  188. package/src/tools/memory-block.tool.ts +14 -6
  189. package/src/tools/plan-approval.tool.ts +49 -47
  190. package/src/tools/read-file-parts.tool.ts +44 -33
  191. package/src/tools/remember-memory.tool.ts +65 -45
  192. package/src/tools/search-web.tool.ts +26 -22
  193. package/src/tools/search.tool.ts +41 -29
  194. package/src/tools/team-think.tool.ts +124 -83
  195. package/src/tools/user-questions.tool.ts +4 -3
  196. package/src/tools/web-tool-shared.ts +6 -0
  197. package/src/utils/async.ts +17 -23
  198. package/src/utils/crypto.ts +21 -0
  199. package/src/utils/date-time.ts +40 -1
  200. package/src/utils/errors.ts +95 -16
  201. package/src/utils/hono-error-handler.ts +24 -39
  202. package/src/utils/index.ts +2 -1
  203. package/src/utils/null-proto-record.ts +41 -0
  204. package/src/utils/sse-keepalive.ts +124 -21
  205. package/src/workers/bootstrap.ts +186 -51
  206. package/src/workers/memory-consolidation.worker.ts +325 -237
  207. package/src/workers/organization-learning.worker.ts +50 -16
  208. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  209. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  210. package/src/workers/skill-extraction.runner.ts +176 -93
  211. package/src/workers/utils/file-section-chunker.ts +8 -10
  212. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  213. package/src/workers/utils/repomix-file-sections.ts +2 -2
  214. package/src/workers/utils/thread-message-query.ts +97 -38
  215. package/src/workers/worker-utils.ts +56 -31
  216. package/src/config/debug-logger.ts +0 -47
  217. package/src/redis/connection-accessor.ts +0 -26
  218. package/src/runtime/context-compaction-runtime.ts +0 -87
  219. package/src/runtime/social-chat-agent-runner.ts +0 -118
  220. package/src/runtime/social-chat.ts +0 -516
  221. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  222. package/src/services/adaptive-playbook.service.ts +0 -152
  223. package/src/services/artifact-provenance.service.ts +0 -172
  224. package/src/services/chat-attachments.service.ts +0 -17
  225. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  226. package/src/services/execution-plan.service.ts +0 -1118
  227. package/src/services/memory.service.ts +0 -844
  228. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  229. package/src/services/plan-agent-query.service.ts +0 -267
  230. package/src/services/plan-approval.service.ts +0 -83
  231. package/src/services/plan-artifact.service.ts +0 -50
  232. package/src/services/plan-builder.service.ts +0 -67
  233. package/src/services/plan-checkpoint.service.ts +0 -81
  234. package/src/services/plan-completion-side-effects.ts +0 -80
  235. package/src/services/plan-coordination.service.ts +0 -157
  236. package/src/services/plan-cycle.service.ts +0 -284
  237. package/src/services/plan-deadline.service.ts +0 -430
  238. package/src/services/plan-event-delivery.service.ts +0 -166
  239. package/src/services/plan-executor.service.ts +0 -1950
  240. package/src/services/plan-run.service.ts +0 -515
  241. package/src/services/plan-scheduler.service.ts +0 -240
  242. package/src/services/plan-template.service.ts +0 -177
  243. package/src/services/plan-validator.service.ts +0 -818
  244. package/src/services/plan-workspace.service.ts +0 -83
  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()