@memberjunction/server 6.2.0-edge.4 → 6.2.0-edge.5
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/README.md +20 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +91 -30
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +311 -81
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +5 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +13 -1
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +25 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/config.d.ts +136 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -4
- package/dist/config.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +142 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +369 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1750 -7
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +103 -44
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +264 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +12 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -160
- package/dist/index.js.map +1 -1
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +2 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -1
- package/dist/realtimeSessions/index.js +2 -0
- package/dist/realtimeSessions/index.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +6 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +7 -3
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +18 -3
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +100 -35
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +82 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +279 -101
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +13 -0
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +2 -8
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +10 -26
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +35 -6
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +65 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +102 -24
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -12
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +54 -2
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +598 -15
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts.map +1 -1
- package/dist/sharedCache.js +3 -0
- package/dist/sharedCache.js.map +1 -1
- package/package.json +99 -98
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +163 -15
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +209 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +1 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +5 -3
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +365 -10
- package/src/__tests__/SessionManager.test.ts +27 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +356 -22
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
- package/src/__tests__/sharedCache.test.ts +5 -3
- package/src/agentSessions/SessionJanitor.ts +369 -93
- package/src/agentSessions/SessionManager.ts +13 -1
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/config.ts +46 -5
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1273 -10
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +304 -325
- package/src/generic/RunViewResolver.ts +17 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +116 -174
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/index.ts +2 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +7 -3
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +121 -39
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +10 -33
- package/src/resolvers/RunAIAgentResolver.ts +39 -4
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/rest/RealtimeRecordingHandler.ts +464 -0
- package/src/rest/ViewOperationsHandler.ts +29 -13
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +704 -14
- package/src/sharedCache.ts +3 -0
|
@@ -9,11 +9,28 @@
|
|
|
9
9
|
* compare-and-swap that marks a completion delivered; an escaping error would either abort that
|
|
10
10
|
* guard or leave the graph looking undelivered and re-notifying on every later sweep.
|
|
11
11
|
*/
|
|
12
|
+
import 'reflect-metadata';
|
|
12
13
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
14
|
|
|
15
|
+
const { fromMetadataProvider, runAgent, processAgentArtifacts } = vi.hoisted(() => ({
|
|
16
|
+
fromMetadataProvider: vi.fn(),
|
|
17
|
+
runAgent: vi.fn(),
|
|
18
|
+
processAgentArtifacts: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
14
21
|
vi.mock('@memberjunction/core', () => ({
|
|
15
22
|
LogError: vi.fn(),
|
|
16
23
|
LogStatus: vi.fn(),
|
|
24
|
+
RunView: {
|
|
25
|
+
FromMetadataProvider: fromMetadataProvider,
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
30
|
+
UserCache: {
|
|
31
|
+
Instance: { Users: [{ ID: 'owner-user-99', Email: 'owner@example.com' }] },
|
|
32
|
+
Users: [{ ID: 'owner-user-99', Email: 'owner@example.com' }],
|
|
33
|
+
},
|
|
17
34
|
}));
|
|
18
35
|
|
|
19
36
|
vi.mock('@memberjunction/core-entities', () => ({
|
|
@@ -24,22 +41,27 @@ vi.mock('@memberjunction/core-entities', () => ({
|
|
|
24
41
|
vi.mock('@memberjunction/ai-core-plus', () => ({ MJAIAgentEntityExtended: class {} }));
|
|
25
42
|
vi.mock('@memberjunction/ai', () => ({ ChatMessageRole: { user: 'user' } }));
|
|
26
43
|
|
|
27
|
-
const runAgent = vi.fn().mockResolvedValue({ success: true });
|
|
28
44
|
vi.mock('@memberjunction/ai-agents', () => ({
|
|
29
|
-
AgentRunner: class {
|
|
45
|
+
AgentRunner: class {
|
|
46
|
+
RunAgent = runAgent;
|
|
47
|
+
ProcessAgentArtifacts = processAgentArtifacts;
|
|
48
|
+
},
|
|
30
49
|
}));
|
|
31
50
|
|
|
32
51
|
import { TaskGraphContinuationDeliverer } from '../services/TaskGraphContinuationDeliverer';
|
|
33
52
|
import type { TaskContinuationParams } from '@memberjunction/task-graph';
|
|
34
53
|
import type { UserInfo } from '@memberjunction/core';
|
|
54
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
35
55
|
|
|
36
56
|
/** A conversation-detail row that can be loaded (the source) and saved (the reply). */
|
|
37
57
|
function detailRow(over: Partial<{ loads: boolean; saves: boolean; conversationID: string; branchID: string | null }> = {}) {
|
|
38
58
|
const { loads = true, saves = true, conversationID = 'conv-1', branchID = null } = over;
|
|
39
59
|
return {
|
|
60
|
+
ID: 'reply-detail-1',
|
|
40
61
|
ConversationID: conversationID,
|
|
41
62
|
BranchID: branchID as string | null,
|
|
42
63
|
Role: '', Status: '', HiddenToUser: true, Message: '',
|
|
64
|
+
AgentID: null as string | null,
|
|
43
65
|
NewRecord: vi.fn(),
|
|
44
66
|
Load: vi.fn().mockResolvedValue(loads),
|
|
45
67
|
Save: vi.fn().mockResolvedValue(saves),
|
|
@@ -60,13 +82,13 @@ function harness(over: Parameters<typeof detailRow>[0] = {}) {
|
|
|
60
82
|
}),
|
|
61
83
|
};
|
|
62
84
|
const providerFactory = { CreateProvider: vi.fn().mockResolvedValue(provider) };
|
|
85
|
+
const pubSub = { publish: vi.fn().mockResolvedValue(undefined) } as unknown as PubSubEngine;
|
|
63
86
|
const deliverer = new TaskGraphContinuationDeliverer(
|
|
64
87
|
providerFactory as never,
|
|
65
88
|
{ ID: 'user-1' } as UserInfo,
|
|
89
|
+
pubSub,
|
|
66
90
|
);
|
|
67
|
-
|
|
68
|
-
// 'stale' fork, so a test sees the deliverer set it.
|
|
69
|
-
return { deliverer, providerFactory, provider, rows, reply: () => rows[1] };
|
|
91
|
+
return { deliverer, providerFactory, provider, rows, pubSub, reply: () => rows[1] };
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
const params = (over: Partial<TaskContinuationParams> = {}): TaskContinuationParams => ({
|
|
@@ -83,10 +105,21 @@ const params = (over: Partial<TaskContinuationParams> = {}): TaskContinuationPar
|
|
|
83
105
|
...over,
|
|
84
106
|
});
|
|
85
107
|
|
|
86
|
-
beforeEach(() =>
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
vi.clearAllMocks();
|
|
110
|
+
fromMetadataProvider.mockReturnValue({
|
|
111
|
+
RunView: vi.fn().mockResolvedValue({ Success: false, Results: [] }),
|
|
112
|
+
});
|
|
113
|
+
runAgent.mockResolvedValue({
|
|
114
|
+
success: true,
|
|
115
|
+
payload: 'Synthesized workflow outcome',
|
|
116
|
+
agentRun: { ID: 'cont-run-1', Message: 'Synthesized workflow outcome' },
|
|
117
|
+
});
|
|
118
|
+
processAgentArtifacts.mockResolvedValue(undefined);
|
|
119
|
+
});
|
|
87
120
|
|
|
88
121
|
describe('posting the outcome', () => {
|
|
89
|
-
it('writes an AI-role message into the graph\'s conversation', async () => {
|
|
122
|
+
it('writes an AI-role message into the graph\'s conversation and broadcasts completion via pubSub', async () => {
|
|
90
123
|
const h = harness();
|
|
91
124
|
await h.deliverer.PostMessage(params());
|
|
92
125
|
|
|
@@ -96,6 +129,7 @@ describe('posting the outcome', () => {
|
|
|
96
129
|
expect(reply.Role).toBe('AI');
|
|
97
130
|
expect(reply.Status).toBe('Complete');
|
|
98
131
|
expect(reply.HiddenToUser).toBe(false);
|
|
132
|
+
expect(h.pubSub.publish).toHaveBeenCalledTimes(2);
|
|
99
133
|
});
|
|
100
134
|
|
|
101
135
|
it('names the workflow and carries the roll-up', async () => {
|
|
@@ -106,8 +140,6 @@ describe('posting the outcome', () => {
|
|
|
106
140
|
});
|
|
107
141
|
|
|
108
142
|
it('lists each step, not just an aggregate status', async () => {
|
|
109
|
-
// A graph where nine of ten steps succeeded is a materially different message from one that
|
|
110
|
-
// failed outright, and the roll-up alone cannot say which step went wrong.
|
|
111
143
|
const h = harness();
|
|
112
144
|
await h.deliverer.PostMessage(params({
|
|
113
145
|
Tasks: [
|
|
@@ -130,7 +162,6 @@ describe('posting the outcome', () => {
|
|
|
130
162
|
const message = h.reply().Message;
|
|
131
163
|
expect(message).toContain('Step 0');
|
|
132
164
|
expect(message).not.toContain('Step 49');
|
|
133
|
-
// The count is stated rather than silently dropped — the task rows remain the full record.
|
|
134
165
|
expect(message).toContain('30 more');
|
|
135
166
|
});
|
|
136
167
|
|
|
@@ -147,8 +178,6 @@ describe('posting the outcome', () => {
|
|
|
147
178
|
});
|
|
148
179
|
|
|
149
180
|
it('mints a fresh provider per delivery', async () => {
|
|
150
|
-
// Deliveries run outside any request and concurrently with task execution; a shared provider
|
|
151
|
-
// would share one transaction scope across unrelated work.
|
|
152
181
|
const h = harness();
|
|
153
182
|
await h.deliverer.PostMessage(params());
|
|
154
183
|
expect(h.providerFactory.CreateProvider).toHaveBeenCalledTimes(1);
|
|
@@ -157,8 +186,6 @@ describe('posting the outcome', () => {
|
|
|
157
186
|
|
|
158
187
|
describe('it never throws — the dispatcher marks delivery inside a CAS guard', () => {
|
|
159
188
|
it('does nothing for a headless graph, without treating it as an error', async () => {
|
|
160
|
-
// A graph submitted by a schedule, an entity-change trigger or an API call has no
|
|
161
|
-
// conversation to answer. Most workflows are in this shape.
|
|
162
189
|
const h = harness();
|
|
163
190
|
await expect(h.deliverer.PostMessage(params({ ConversationDetailID: null }))).resolves.toBeUndefined();
|
|
164
191
|
expect(h.providerFactory.CreateProvider).not.toHaveBeenCalled();
|
|
@@ -167,7 +194,7 @@ describe('it never throws — the dispatcher marks delivery inside a CAS guard',
|
|
|
167
194
|
it('survives a conversation detail that will not load', async () => {
|
|
168
195
|
const h = harness({ loads: false });
|
|
169
196
|
await expect(h.deliverer.PostMessage(params())).resolves.toBeUndefined();
|
|
170
|
-
expect(h.rows.length).toBe(1);
|
|
197
|
+
expect(h.rows.length).toBe(1);
|
|
171
198
|
});
|
|
172
199
|
|
|
173
200
|
it('survives a failed save', async () => {
|
|
@@ -185,7 +212,6 @@ describe('it never throws — the dispatcher marks delivery inside a CAS guard',
|
|
|
185
212
|
});
|
|
186
213
|
|
|
187
214
|
describe('Reinvoke — restarting the submitting agent', () => {
|
|
188
|
-
/** A provider that hands out loadable agent-run and agent rows, then the reply detail. */
|
|
189
215
|
function reinvokeHarness(over: { runLoads?: boolean; agentLoads?: boolean } = {}) {
|
|
190
216
|
const { runLoads = true, agentLoads = true } = over;
|
|
191
217
|
const rows: Array<Record<string, unknown>> = [];
|
|
@@ -208,14 +234,16 @@ describe('Reinvoke — restarting the submitting agent', () => {
|
|
|
208
234
|
return detail;
|
|
209
235
|
}),
|
|
210
236
|
};
|
|
237
|
+
const pubSub = { publish: vi.fn().mockResolvedValue(undefined) } as unknown as PubSubEngine;
|
|
211
238
|
const deliverer = new TaskGraphContinuationDeliverer(
|
|
212
239
|
{ CreateProvider: vi.fn().mockResolvedValue(provider) } as never,
|
|
213
240
|
{ ID: 'user-1' } as UserInfo,
|
|
241
|
+
pubSub,
|
|
214
242
|
);
|
|
215
|
-
return { deliverer, provider, rows };
|
|
243
|
+
return { deliverer, provider, rows, pubSub };
|
|
216
244
|
}
|
|
217
245
|
|
|
218
|
-
it('starts the submitting agent a fresh turn carrying the outcome', async () => {
|
|
246
|
+
it('starts the submitting agent a fresh turn carrying the outcome, saves reply detail, processes artifacts, and publishes completion', async () => {
|
|
219
247
|
const h = reinvokeHarness();
|
|
220
248
|
await h.deliverer.Reinvoke(params());
|
|
221
249
|
|
|
@@ -223,11 +251,17 @@ describe('Reinvoke — restarting the submitting agent', () => {
|
|
|
223
251
|
const call = runAgent.mock.calls[0][0];
|
|
224
252
|
expect(call.agent.ID).toBe('agent-1');
|
|
225
253
|
expect(call.conversationMessages[0].content).toContain('Weekly digest');
|
|
254
|
+
expect(call.conversationDetailId).toBe('reply-detail-1');
|
|
255
|
+
|
|
256
|
+
const reply = h.rows[3] as ReturnType<typeof detailRow>;
|
|
257
|
+
expect(reply.Role).toBe('AI');
|
|
258
|
+
expect(reply.Status).toBe('Complete');
|
|
259
|
+
expect(reply.Message).toBe('Synthesized workflow outcome');
|
|
260
|
+
expect(processAgentArtifacts).toHaveBeenCalledTimes(1);
|
|
261
|
+
expect(h.pubSub.publish).toHaveBeenCalledTimes(2);
|
|
226
262
|
});
|
|
227
263
|
|
|
228
264
|
it('stamps depth + 1 — the value that makes MAX_REINVOKE_DEPTH real', async () => {
|
|
229
|
-
// Without this the next graph the restarted run submits begins the chain at zero again, and
|
|
230
|
-
// the cap can never fire. It is the whole reason ContinuationDepth exists as a column.
|
|
231
265
|
const h = reinvokeHarness();
|
|
232
266
|
await h.deliverer.Reinvoke(params({ ReinvokeDepth: 3 }));
|
|
233
267
|
expect(runAgent.mock.calls[0][0].continuationDepth).toBe(4);
|
|
@@ -240,7 +274,6 @@ describe('Reinvoke — restarting the submitting agent', () => {
|
|
|
240
274
|
});
|
|
241
275
|
|
|
242
276
|
it('falls back to posting when the graph records no submitting run', async () => {
|
|
243
|
-
// A schedule- or trigger-started graph has no turn to continue.
|
|
244
277
|
const h = reinvokeHarness();
|
|
245
278
|
await h.deliverer.Reinvoke(params({ SubmittedByAgentRunID: null }));
|
|
246
279
|
expect(runAgent).not.toHaveBeenCalled();
|
|
@@ -266,7 +299,6 @@ describe('Reinvoke — restarting the submitting agent', () => {
|
|
|
266
299
|
});
|
|
267
300
|
|
|
268
301
|
describe('Reinvoke — on the fork of the message that started the graph', () => {
|
|
269
|
-
/** A provider whose agent run and agent load, and whose origin detail sits on `originBranch` (null is Main). */
|
|
270
302
|
function forkHarness(originBranch: string | null) {
|
|
271
303
|
const provider = {
|
|
272
304
|
GetEntityObject: vi.fn().mockImplementation(async (name: string) => {
|
|
@@ -291,6 +323,308 @@ describe('Reinvoke — on the fork of the message that started the graph', () =>
|
|
|
291
323
|
it('leaves the branch unset when that message is in Main', async () => {
|
|
292
324
|
await forkHarness(null).Reinvoke(params());
|
|
293
325
|
|
|
326
|
+
expect(runAgent).toHaveBeenCalledTimes(1);
|
|
294
327
|
expect(runAgent.mock.calls[0][0].ConversationBranchID).toBeUndefined();
|
|
295
328
|
});
|
|
329
|
+
|
|
330
|
+
it('enriches placeholder "output available (...)" task summaries with OutputPayload excerpts from MJ: Tasks', async () => {
|
|
331
|
+
const { RunView } = await import('@memberjunction/core');
|
|
332
|
+
vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
|
|
333
|
+
RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
|
|
334
|
+
if (rvParams.EntityName === 'MJ: Tasks') {
|
|
335
|
+
return {
|
|
336
|
+
Success: true,
|
|
337
|
+
Results: [
|
|
338
|
+
{
|
|
339
|
+
ID: 't1',
|
|
340
|
+
Name: 'Generate Schema Count Query',
|
|
341
|
+
OutputPayload: JSON.stringify({
|
|
342
|
+
title: 'Entity Counts by Schema',
|
|
343
|
+
request: 'SELECT SchemaName, COUNT(*) AS EntityCount FROM __mj.vwEntities GROUP BY SchemaName',
|
|
344
|
+
}),
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return { Success: false, Results: [] };
|
|
350
|
+
}),
|
|
351
|
+
} as never);
|
|
352
|
+
|
|
353
|
+
await forkHarness(null).Reinvoke(
|
|
354
|
+
params({
|
|
355
|
+
Tasks: [
|
|
356
|
+
{ TaskID: 't1', Name: 'Generate Schema Count Query', Status: 'Complete', Summary: 'output available (180 chars)' },
|
|
357
|
+
],
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect(runAgent).toHaveBeenCalledTimes(1);
|
|
362
|
+
const sentContent = runAgent.mock.calls[0][0].conversationMessages[0].content as string;
|
|
363
|
+
expect(sentContent).toContain('Title: Entity Counts by Schema');
|
|
364
|
+
expect(sentContent).toContain('SELECT SchemaName, COUNT(*) AS EntityCount FROM __mj.vwEntities GROUP BY SchemaName');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('updates the originating message in-place when no intervening messages were sent (IsLatest = true)', async () => {
|
|
368
|
+
const { RunView } = await import('@memberjunction/core');
|
|
369
|
+
vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
|
|
370
|
+
RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
|
|
371
|
+
if (rvParams.EntityName === 'MJ: Conversation Details') {
|
|
372
|
+
return {
|
|
373
|
+
Success: true,
|
|
374
|
+
Results: [{ ID: 'detail-1' }],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return { Success: false, Results: [] };
|
|
378
|
+
}),
|
|
379
|
+
} as never);
|
|
380
|
+
|
|
381
|
+
const originDetail = {
|
|
382
|
+
...detailRow(),
|
|
383
|
+
ID: 'detail-1',
|
|
384
|
+
Message: "Started **Weekly digest** — 2 task(s) running. I'll follow up when it finishes.",
|
|
385
|
+
};
|
|
386
|
+
const createdDetails: Array<ReturnType<typeof detailRow>> = [];
|
|
387
|
+
const provider = {
|
|
388
|
+
GetEntityObject: vi.fn().mockImplementation(async (name: string) => {
|
|
389
|
+
if (name === 'MJ: AI Agent Runs') return { AgentID: 'agent-1', Load: vi.fn().mockResolvedValue(true) };
|
|
390
|
+
if (name === 'MJ: AI Agents') return { ID: 'agent-1', Load: vi.fn().mockResolvedValue(true) };
|
|
391
|
+
if (createdDetails.length === 0) {
|
|
392
|
+
createdDetails.push(originDetail);
|
|
393
|
+
return originDetail;
|
|
394
|
+
}
|
|
395
|
+
const next = detailRow();
|
|
396
|
+
createdDetails.push(next);
|
|
397
|
+
return next;
|
|
398
|
+
}),
|
|
399
|
+
};
|
|
400
|
+
const pubSub = { publish: vi.fn().mockResolvedValue(undefined) } as unknown as PubSubEngine;
|
|
401
|
+
const deliverer = new TaskGraphContinuationDeliverer(
|
|
402
|
+
{ CreateProvider: vi.fn().mockResolvedValue(provider) } as never,
|
|
403
|
+
{ ID: 'user-1' } as UserInfo,
|
|
404
|
+
pubSub,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
await deliverer.Reinvoke(params());
|
|
408
|
+
|
|
409
|
+
// Only the originating detail was loaded and updated in-place — no second detail row created!
|
|
410
|
+
expect(createdDetails.length).toBe(1);
|
|
411
|
+
expect(originDetail.NewRecord).not.toHaveBeenCalled();
|
|
412
|
+
expect(originDetail.Message).toBe('Synthesized workflow outcome');
|
|
413
|
+
expect(originDetail.Status).toBe('Complete');
|
|
414
|
+
expect(runAgent.mock.calls[0][0].conversationDetailId).toBe('detail-1');
|
|
415
|
+
expect(runAgent.mock.calls[0][0].inputArtifacts).toEqual([]);
|
|
416
|
+
expect(pubSub.publish).toHaveBeenCalledTimes(1);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('creates a bottom continuation reply with ParentID = originDetailId and updates the origin message note when intervening messages exist (IsLatest = false)', async () => {
|
|
420
|
+
const { RunView } = await import('@memberjunction/core');
|
|
421
|
+
vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
|
|
422
|
+
RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
|
|
423
|
+
if (rvParams.EntityName === 'MJ: Conversation Details') {
|
|
424
|
+
return {
|
|
425
|
+
Success: true,
|
|
426
|
+
Results: [{ ID: 'intervening-user-msg-2' }],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return { Success: false, Results: [] };
|
|
430
|
+
}),
|
|
431
|
+
} as never);
|
|
432
|
+
|
|
433
|
+
const originDetail = {
|
|
434
|
+
...detailRow(),
|
|
435
|
+
ID: 'detail-1',
|
|
436
|
+
ParentID: null as string | null,
|
|
437
|
+
Message: "Started **Weekly digest** — 2 task(s) running. I'll follow up when it finishes.",
|
|
438
|
+
};
|
|
439
|
+
const replyDetail = {
|
|
440
|
+
...detailRow(),
|
|
441
|
+
ID: 'bottom-reply-3',
|
|
442
|
+
ParentID: null as string | null,
|
|
443
|
+
};
|
|
444
|
+
let detailCallCount = 0;
|
|
445
|
+
const provider = {
|
|
446
|
+
GetEntityObject: vi.fn().mockImplementation(async (name: string) => {
|
|
447
|
+
if (name === 'MJ: AI Agent Runs') return { AgentID: 'agent-1', Load: vi.fn().mockResolvedValue(true) };
|
|
448
|
+
if (name === 'MJ: AI Agents') return { ID: 'agent-1', Load: vi.fn().mockResolvedValue(true) };
|
|
449
|
+
detailCallCount++;
|
|
450
|
+
return detailCallCount === 1 ? originDetail : replyDetail;
|
|
451
|
+
}),
|
|
452
|
+
};
|
|
453
|
+
const pubSub = { publish: vi.fn().mockResolvedValue(undefined) } as unknown as PubSubEngine;
|
|
454
|
+
const deliverer = new TaskGraphContinuationDeliverer(
|
|
455
|
+
{ CreateProvider: vi.fn().mockResolvedValue(provider) } as never,
|
|
456
|
+
{ ID: 'user-1' } as UserInfo,
|
|
457
|
+
pubSub,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
await deliverer.Reinvoke(params());
|
|
461
|
+
|
|
462
|
+
expect(replyDetail.NewRecord).toHaveBeenCalledTimes(1);
|
|
463
|
+
expect(replyDetail.ParentID).toBe('detail-1');
|
|
464
|
+
expect(replyDetail.Message).toBe('Synthesized workflow outcome');
|
|
465
|
+
expect(originDetail.Message).toContain('*(✅ **Weekly digest** completed — synthesized results posted below.)*');
|
|
466
|
+
expect(originDetail.Message).not.toContain("I'll follow up when it finishes");
|
|
467
|
+
expect(pubSub.publish).toHaveBeenCalledTimes(2);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('strips inherited upstream payload keys across DAG steps and extracts nested synthesis and report fields', async () => {
|
|
471
|
+
const { RunView } = await import('@memberjunction/core');
|
|
472
|
+
const step1Payload = {
|
|
473
|
+
queryName: 'Annual Order Counts',
|
|
474
|
+
sql: 'SELECT YEAR(OrderDate) AS OrderYear, COUNT(*) AS OrderCount FROM Orders GROUP BY YEAR(OrderDate)',
|
|
475
|
+
explanation: 'Counts orders grouped by year.',
|
|
476
|
+
};
|
|
477
|
+
const step2Payload = {
|
|
478
|
+
...step1Payload,
|
|
479
|
+
synthesis: {
|
|
480
|
+
executiveSummary: 'Order volume grew 22% YoY between 2022 and 2025.',
|
|
481
|
+
keyFindings: ['2024 peak growth', 'Steady enterprise retention'],
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
const step3Payload = {
|
|
485
|
+
...step2Payload,
|
|
486
|
+
report: {
|
|
487
|
+
name: 'Annual Order Trend Executive Report',
|
|
488
|
+
description: 'Board-ready HTML report covering annual order volume and YoY trajectory.',
|
|
489
|
+
html: '<h1>Annual Order Trend</h1><p>Executive overview of multi-year order growth.</p>',
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
|
|
494
|
+
RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
|
|
495
|
+
if (rvParams.EntityName === 'MJ: Tasks') {
|
|
496
|
+
return {
|
|
497
|
+
Success: true,
|
|
498
|
+
Results: [
|
|
499
|
+
{ ID: 't1', Name: 'Step 1: Query', OutputPayload: JSON.stringify(step1Payload) },
|
|
500
|
+
{ ID: 't2', Name: 'Step 2: Strategy', OutputPayload: JSON.stringify(step2Payload) },
|
|
501
|
+
{ ID: 't3', Name: 'Step 3: Report', OutputPayload: JSON.stringify(step3Payload) },
|
|
502
|
+
],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return { Success: false, Results: [] };
|
|
506
|
+
}),
|
|
507
|
+
} as never);
|
|
508
|
+
|
|
509
|
+
await forkHarness(null).Reinvoke(
|
|
510
|
+
params({
|
|
511
|
+
Tasks: [
|
|
512
|
+
{ TaskID: 't1', Name: 'Step 1: Query', Status: 'Complete', Summary: 'output available (180 chars)' },
|
|
513
|
+
{ TaskID: 't2', Name: 'Step 2: Strategy', Status: 'Complete', Summary: 'output available (320 chars)' },
|
|
514
|
+
{ TaskID: 't3', Name: 'Step 3: Report', Status: 'Complete', Summary: 'output available (540 chars)' },
|
|
515
|
+
],
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const sentContent = runAgent.mock.calls[0][0].conversationMessages[0].content as string;
|
|
520
|
+
expect(sentContent).toContain('Title: Annual Order Counts');
|
|
521
|
+
expect(sentContent).toContain('Executive Summary: Order volume grew 22% YoY between 2022 and 2025.');
|
|
522
|
+
expect(sentContent).toContain('Report Title: Annual Order Trend Executive Report');
|
|
523
|
+
expect(sentContent).toContain('Report Summary: Board-ready HTML report covering annual order volume and YoY trajectory.');
|
|
524
|
+
// Step 1's SQL should only appear once (under Step 1), not duplicated under Step 2 and Step 3!
|
|
525
|
+
const sqlOccurrences = sentContent.split('SELECT YEAR(OrderDate) AS OrderYear').length - 1;
|
|
526
|
+
expect(sqlOccurrences).toBe(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('walks up ParentRunID so a sub-agent (Workflow Planner) submission reinvokes the root conversation agent (Sage) as the conversation owner', async () => {
|
|
530
|
+
const subAgentRun = {
|
|
531
|
+
ID: 'subagent-planner-run',
|
|
532
|
+
AgentID: 'workflow-planner-agent-id',
|
|
533
|
+
ParentRunID: 'root-sage-run',
|
|
534
|
+
UserID: null as string | null,
|
|
535
|
+
Load: vi.fn().mockResolvedValue(true),
|
|
536
|
+
};
|
|
537
|
+
const rootSageRun = {
|
|
538
|
+
ID: 'root-sage-run',
|
|
539
|
+
AgentID: 'sage-agent-id',
|
|
540
|
+
ParentRunID: null as string | null,
|
|
541
|
+
UserID: 'owner-user-99',
|
|
542
|
+
Load: vi.fn().mockResolvedValue(true),
|
|
543
|
+
};
|
|
544
|
+
const sageAgent = {
|
|
545
|
+
ID: 'sage-agent-id',
|
|
546
|
+
Name: 'Sage',
|
|
547
|
+
Load: vi.fn().mockResolvedValue(true),
|
|
548
|
+
};
|
|
549
|
+
const detail = detailRow();
|
|
550
|
+
let runCallCount = 0;
|
|
551
|
+
const detailUsers: Array<UserInfo | undefined> = [];
|
|
552
|
+
|
|
553
|
+
const provider = {
|
|
554
|
+
GetEntityObject: vi.fn().mockImplementation(async (name: string, user?: UserInfo) => {
|
|
555
|
+
if (name === 'MJ: AI Agent Runs') {
|
|
556
|
+
runCallCount++;
|
|
557
|
+
return runCallCount === 1 ? subAgentRun : rootSageRun;
|
|
558
|
+
}
|
|
559
|
+
if (name === 'MJ: AI Agents') {
|
|
560
|
+
return sageAgent;
|
|
561
|
+
}
|
|
562
|
+
detailUsers.push(user);
|
|
563
|
+
return detail;
|
|
564
|
+
}),
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const deliverer = new TaskGraphContinuationDeliverer(
|
|
568
|
+
{ CreateProvider: vi.fn().mockResolvedValue(provider) } as never,
|
|
569
|
+
{ ID: 'system-user' } as UserInfo,
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
await deliverer.Reinvoke(params({ SubmittedByAgentRunID: 'subagent-planner-run' }));
|
|
573
|
+
|
|
574
|
+
expect(subAgentRun.Load).toHaveBeenCalledWith('subagent-planner-run');
|
|
575
|
+
expect(rootSageRun.Load).toHaveBeenCalledWith('root-sage-run');
|
|
576
|
+
expect(sageAgent.Load).toHaveBeenCalledWith('sage-agent-id');
|
|
577
|
+
expect(runAgent).toHaveBeenCalledTimes(1);
|
|
578
|
+
expect(runAgent.mock.calls[0][0].agent.ID).toBe('sage-agent-id');
|
|
579
|
+
expect(runAgent.mock.calls[0][0].contextUser.ID).toBe('owner-user-99');
|
|
580
|
+
expect(detailUsers[0]?.ID).toBe('owner-user-99');
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('enriches Skipped steps with their unmet dependency Condition from MJ: Task Dependencies', async () => {
|
|
584
|
+
const { RunView } = await import('@memberjunction/core');
|
|
585
|
+
vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
|
|
586
|
+
RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
|
|
587
|
+
if (rvParams.EntityName === 'MJ: Tasks') {
|
|
588
|
+
return {
|
|
589
|
+
Success: true,
|
|
590
|
+
Results: [
|
|
591
|
+
{ ID: 't1', Name: 'Build Query', Status: 'Complete', OutputPayload: JSON.stringify({ queryName: 'Users by Role' }) },
|
|
592
|
+
{ ID: 't2', Name: 'Run Query', Status: 'Complete', OutputPayload: JSON.stringify({ summary: 'Built query blueprint without live execution' }) },
|
|
593
|
+
{ ID: 't3', Name: 'Analyze Results', Status: 'Skipped', OutputPayload: null },
|
|
594
|
+
],
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
if (rvParams.EntityName === 'MJ: Task Dependencies') {
|
|
598
|
+
return {
|
|
599
|
+
Success: true,
|
|
600
|
+
Results: [
|
|
601
|
+
{
|
|
602
|
+
TaskID: 't3',
|
|
603
|
+
DependsOnTaskID: 't2',
|
|
604
|
+
Condition: 'output.rowCount > 0',
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return { Success: false, Results: [] };
|
|
610
|
+
}),
|
|
611
|
+
} as never);
|
|
612
|
+
|
|
613
|
+
await forkHarness(null).Reinvoke(
|
|
614
|
+
params({
|
|
615
|
+
Tasks: [
|
|
616
|
+
{ TaskID: 't1', Name: 'Build Query', Status: 'Complete' },
|
|
617
|
+
{ TaskID: 't2', Name: 'Run Query', Status: 'Complete' },
|
|
618
|
+
{ TaskID: 't3', Name: 'Analyze Results', Status: 'Skipped' },
|
|
619
|
+
],
|
|
620
|
+
Summary: '2 of 3 steps completed (1 skipped).',
|
|
621
|
+
}),
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
const sentContent = runAgent.mock.calls[0][0].conversationMessages[0].content as string;
|
|
625
|
+
expect(sentContent).toContain('- ⏭️ **Analyze Results** — Skipped: branch condition `output.rowCount > 0` on "Run Query" evaluated to false');
|
|
626
|
+
expect(sentContent).toContain('briefly explaining any conditional steps that were skipped');
|
|
627
|
+
});
|
|
296
628
|
});
|
|
629
|
+
|
|
630
|
+
|
|
@@ -133,4 +133,26 @@ describe('TaskGraphFrameBroadcaster', () => {
|
|
|
133
133
|
expect(p.CompletedCount).toBe(7);
|
|
134
134
|
expect(p.TotalCount).toBe(7);
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
it('bridges frames onto PUSH_STATUS_UPDATES_TOPIC when bridgeToPushStatus is enabled', () => {
|
|
138
|
+
new TaskGraphFrameBroadcaster(pubSub, true).OnFrame(
|
|
139
|
+
frame({ Kind: 'TaskCompleted', Status: 'Complete', CompletedCount: 1, TotalCount: 2 }),
|
|
140
|
+
);
|
|
141
|
+
expect(published).toHaveLength(2);
|
|
142
|
+
expect(published[0][0]).toBe(TASK_GRAPH_FRAMES_TOPIC);
|
|
143
|
+
expect(published[1][0]).toBe('PUSH_STATUS_UPDATES');
|
|
144
|
+
const pushPayload = published[1][1] as { sessionId: string; ownerUserId: string; message: string };
|
|
145
|
+
expect(pushPayload.sessionId).toBe('*');
|
|
146
|
+
expect(pushPayload.ownerUserId).toBe(OWNER);
|
|
147
|
+
const parsed = JSON.parse(pushPayload.message) as {
|
|
148
|
+
type: string;
|
|
149
|
+
status: string;
|
|
150
|
+
data: { Status: string; TaskID: string };
|
|
151
|
+
};
|
|
152
|
+
expect(parsed.type).toBe('TaskGraphFrame');
|
|
153
|
+
expect(parsed.status).toBe('ok');
|
|
154
|
+
expect(parsed.data.Status).toBe('Complete');
|
|
155
|
+
expect(parsed.data.TaskID).toBe('task-1');
|
|
156
|
+
});
|
|
136
157
|
});
|
|
158
|
+
|